1 Introduction

Jacques et moi avons beaucoup communiqué sur les modèles d’ensemble échangeables, mais absolument rien valorisé en terme d’article vis à vis de la communauté d’hydrologie statistique que nous nous plaisons tant à critiquer. D’autre part, nos accords (ainsi que désaccords) théoriques sur l’intérêt du caractère échangeable pour modéliser les ensembles et du Bayesian Forecasting System de Krzysztofowicz afin de construire une prédictive resteront du domaine des plaisantes joutes epistolaires limitées à deux crocodiles bayésiens si nous ne montrons pas leur caractère opératoire sur de “vraies” jeux de données. Je propose donc de traiter de tels jeux de données pour lesquels on mettra en évidence les améliorations concrètes et pratiques que ces méthodes apportent (au delà de leur caractère théorique rationel et cohérent). C’est l’objectif de ce papier, écrit en Markdown html notebook dans l’intention de donner une illustration informatique et applicative en même temps que du texte plus théorique. J’imagine en fait deux papiers de ce type:

2 Analyses Descriptives

2.1 Les données disponibles

rm(list=ls())
# setwd("~/Documents/courbariaux/WORKLUC/TempEnsemblesNormal")
load("Ain@Vouglans.Rdata")
library(tidyverse)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 methods overwritten by 'ggplot2':
  method         from 
  [.quosures     rlang
  c.quosures     rlang
  print.quosures rlang
── Attaching packages ─────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.1.1     ✔ purrr   0.3.2
✔ tibble  2.1.3     ✔ dplyr   0.8.1
✔ tidyr   0.8.3     ✔ stringr 1.4.0
✔ readr   1.3.1     ✔ forcats 0.4.0
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(tidyselect)
library(lubridate)

Attaching package: ‘lubridate’

The following object is masked from ‘package:base’:

    date
library(R2jags)
Loading required package: rjags
Loading required package: coda
Linked to JAGS 4.3.0
Loaded modules: basemod,bugs

Attaching package: ‘R2jags’

The following object is masked from ‘package:coda’:

    traceplot
library(verification)
Loading required package: fields
Loading required package: spam
Loading required package: dotCall64
Loading required package: grid
Spam version 2.2-2 (2019-03-07) is loaded.
Type 'help( Spam)' or 'demo( spam)' for a short introduction 
and overview of this package.
Help for individual functions is also obtained by adding the
suffix '.spam' to the function name, e.g. 'help( chol.spam)'.

Attaching package: ‘spam’

The following objects are masked from ‘package:base’:

    backsolve, forwardsolve

Loading required package: maps

Attaching package: ‘maps’

The following object is masked from ‘package:purrr’:

    map

See https://github.com/NCAR/Fields for
 an extensive vignette, other supplements and source code 
Loading required package: boot
Loading required package: CircStats
Loading required package: MASS

Attaching package: ‘MASS’

The following object is masked from ‘package:dplyr’:

    select

Loading required package: dtw
Loading required package: proxy

Attaching package: ‘proxy’

The following object is masked from ‘package:spam’:

    as.matrix

The following objects are masked from ‘package:stats’:

    as.dist, dist

The following object is masked from ‘package:base’:

    as.matrix

Loaded dtw v1.20-1. See ?dtw for help, citation("dtw") for use in publication.
library(GGally)
Registered S3 method overwritten by 'GGally':
  method from   
  +.gg   ggplot2

Attaching package: ‘GGally’

The following object is masked from ‘package:dplyr’:

    nasa

Suite à la thèse de Marie Courbariaux, je dispose des données journalières de températures fournies par EDF-DTG aux stations suivantes:

  • l’ Ain à Vouglans,

  • le Buech à Chambons,

  • le Drac à Sautet.

Je propose de travailler dans toute la suite sur l’Ain à Vouglans, mais les programmes développés sont directement transposables aux deux autres stations. Nous disposons de l’historique journalier sur la période 1953 à 2015 dans le data.frame dhisto, ainsi que des \(50\) membres du Centre Européen de Prévision pour les années 2005 à 2008 (4ans) puis de 2O11 à 1015 (5 ans) pour des prévisions jusqu’à une échéance de 9 jours dans le data.frame dtout.

dhisto %>% glimpse
Observations: 23,010
Variables: 7
$ Date       <date> 1953-01-01, 1953-01-02, 1953-01-03, 1953-01-04, 1953-01-0…
$ Qobs       <dbl> 33.0, 30.0, 29.1, 24.7, 23.7, 23.9, 22.5, 21.9, 19.6, 18.3…
$ PS         <dbl> 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 1.1, 0.0, 4.7, 3.9, 0.0, 0.0…
$ T          <dbl> -0.8, 0.0, -1.4, -1.9, -1.4, -1.8, -3.9, -2.6, -1.6, -1.6,…
$ Year       <dbl> 1953, 1953, 1953, 1953, 1953, 1953, 1953, 1953, 1953, 1953…
$ Month      <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ Calendaire <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,…
range(dhisto$Date)
[1] "1953-01-01" "2015-12-31"
dtout %>% head()
unique(dtout$Year)
 [1] 2005 2006 2007 2008 2009 2011 2012 2013 2014 2015 2016
unique(dtout$Echeance)
[1] 1 2 3 4 5 6 7 8 9

2.2 Analyse climatologique

On peut illustrer les fluctuations saisonnières des températures journalières au cours de l’année:

dhisto %>% mutate(an= as_factor(Year)) %>% ggplot(aes(x = Calendaire, y= T, color = an)) + geom_line(alpha=0.5)

horsain = ymd("2003-10-15")

Ceci met en évidence un comportement très sûrement aberrant en date du 2003-10-15, horsain que l’on enlève

dhisto %>% filter(Date!= horsain) %>% mutate(an= as_factor(Year)) %>% ggplot(aes(x = Calendaire, y= T)) + geom_point(aes( color = an),alpha=0.5, shape ='.', show.legend = FALSE)+geom_smooth()+labs(x='Jours Calendaires', y='Températures', title="Climatologie à Vouglans")

On va alors construire une température moyenne de réference et son écart-type associé, éventuellement lissés pour chaque jour calendaire de l’année. Pour construire ces estimateurs périodiques, on régresse sure une base des 4 premières harmoniques la température interannuelle moyenne et le logarithme de l’écart-type journalier de la température.

dhisto %>% filter(Date!= horsain, Calendaire != 366) %>% mutate(an= as_factor(Year)) %>% group_by(Calendaire) %>% summarize (T_moy_ref= mean(T),T_std_ref= var(T)^0.5 ) -> T_ref

modlin<-lm(T_moy_ref~ 1+ sin(2*pi*Calendaire/365)+                        cos(2*pi*Calendaire/365)+
          sin(4*pi*Calendaire/365) +
          cos(4*pi*Calendaire/365)+
          sin(6*pi*Calendaire/365) +
          cos(6*pi*Calendaire/365)+
          sin(8*pi*Calendaire/365)+
          cos(8*pi*Calendaire/365), data=T_ref
          )
modlinStd <-lm(log(T_std_ref)~ 1+ sin(2*pi*Calendaire/365)+                        cos(2*pi*Calendaire/365)+
          sin(4*pi*Calendaire/365) +
          cos(4*pi*Calendaire/365)+
          sin(6*pi*Calendaire/365) +
          cos(6*pi*Calendaire/365)+
          sin(8*pi*Calendaire/365)+
          cos(8*pi*Calendaire/365), data=T_ref
          )

T_ref %>% mutate(T_moy_ref_lis=modlin$fitted.values,
                 T_std_ref_lis=exp(modlinStd$fitted.values) )-> T_ref
T_ref%>% 
  ggplot(aes(x=Calendaire))+geom_line(aes(y=T_moy_ref_lis)) + geom_point(aes(y=T_moy_ref))+labs(caption="Moyenne calendaire (référence climatologique)")

T_ref %>%  
  ggplot(aes(x=Calendaire))+geom_line(aes(y=T_std_ref_lis)) +    geom_point(aes(y=T_std_ref))+labs(caption="Ecart-Type calendaire (référence climatologique) ")

2.3 Analyse descriptive de l’ensemble du centre européen de prévision

L’analyse climatologique précédente permet de centrer et réduire les températures pour travailler en anomalies journalières. Ces anomalies sans dimension sont alors considérées comme gaussiennes, marginalement \(N(0,1)\). On va laisser tomber les membres individuels pour en conserver les statistiques exhaustives, moyenne et variance empiriques dans le dataframe de travail d. Construisons d’abord une base de données (en ajoutant la climatologie).

dtout %>%  mutate(Xbar=rowMeans(dplyr::select(.,starts_with("Run"))),
                  V2=apply(dplyr::select(.,starts_with("Run")),1,var)) %>%
  dplyr::select(-starts_with("Run")) ->d

2.3.1 Relation entre moyenne de l’ensemble et température observée

Représentons en premier lieu le lien entre température observée et température moyenne prévue à diverses échéances (après centrage par la moyenne climatologique calendaire et réduction par l’écart-type climatologique calendaire )

 d %>% left_join(T_ref %>% dplyr::select(-T_std_ref,-T_moy_ref)) ->d
Joining, by = "Calendaire"
 d %>% ggplot(aes(x=(Obs-T_moy_ref_lis)/T_std_ref_lis,y=(Xbar-T_moy_ref_lis)/T_std_ref_lis,color=as.factor(Month)) )+geom_point()+geom_abline(slope=1,intercept=0) +facet_wrap(~Echeance)+labs(x="Anomalie de température", y="Anomalie de prévision", color="Mois")

Pour la moyenne d’ensemble, on constate que le biais et la dispersion vis à vis de la cible augmentent avec l’échéance, comme on s’y attend.

d %>% mutate(x=(Obs-T_moy_ref_lis)/T_std_ref_lis,y=(Xbar-T_moy_ref_lis)/T_std_ref_lis) %>% group_by(Echeance) %>% summarise(biais=mean(x-y, na.rm=T), variance=var(x-y, na.rm=T), sce=mean((x-y)^2, na.rm=T))

Pour une échéance donnée, il y a également des fluctuations selon le mois considéré, mais ces différences restent relativement raisonnables. A titre d’exemple, faisons le calcul pour une échéance de 4 jours:

d %>% filter(Echeance ==4) %>% mutate(x=(Obs-T_moy_ref_lis)/T_std_ref_lis,y=(Xbar-T_moy_ref_lis)/T_std_ref_lis) %>% 
  group_by(Month) %>% summarise(biais=mean(x-y, na.rm=T), variance=var(x-y, na.rm=T),sce=mean((x-y)^2, na.rm=T))

2.3.2 Relation entre variance de l’ensemble et température observée

Par contre, la variance inter-membre ne semble guère reliée à la température observée

d %>% ggplot(aes(x=(Obs-T_moy_ref_lis)/T_std_ref_lis,y=log(V2/(T_std_ref_lis^2)),color=as.factor(Month)) )+geom_point()+facet_wrap(~Echeance)+labs(x="anomalie de température", y="Variance relative (enLog)", color="Mois")

Comme le montre la figure précédente, la valeur moyenne (géométrique) de la variance inter-membre augmente avec l’échéance jusqu’à atteindre 42% de la variance climatologique à l’échéance 9 jours, comme on s’y attend, mais pas la dispersion de son logarithme, qui, au contraire, diminue .

d %>% mutate(y=log(V2/(T_std_ref_lis^2))) %>% group_by(Echeance) %>% summarise(moygeomvariance=exp(mean(y, na.rm=T)), VarLogVariance=var(y, na.rm=T))

Là encore, le mois de l’année ne semble guère influent vis à vis de ces caractéristiques, voir par exemple le tableau pour l’échéance à quatre jours.

d %>% filter(Echeance ==4) %>% mutate(y=log(V2/(T_std_ref_lis^2))) %>% group_by(Month) %>% summarise(moygeomvariance=exp(mean(y, na.rm=T)), VarLogVariance=var(y, na.rm=T))

2.3.3 Relation entre variance de l’ensemble et écart de la température observée à la moyenne d’ensemble

On observe enfin un lien de l’écart entre observation et moyenne d’ensemble à la variabilité inter-membre. La figure ci-après illustre cette liaison (dans l’échelle des écart-types relatifs). Je reste pour le moment sans explication en ce qui concerne la valeur de la pente, proche de \(1\).

d %>%  mutate(x=(V2/(T_std_ref_lis^2)),y=((Obs-Xbar)/T_std_ref_lis)^2) %>% 
ggplot(aes(x=(x)^0.5,y=(y)^0.5))+geom_point(aes( color=as.factor(Month)))+geom_smooth(se=0, color='black')+geom_abline(intercept=0, slope=1, color='white')+facet_wrap(~Echeance)+labs(y="écart absolu prévision-temperature normalisé", x="std relatif", color="Mois")

On souhaite se servir de ce lien, même s’il s’avère assez flou, pour construire un modèle de prévision probabiliste reliant les statistiques résumées \(\bar{X}_t, V_t^2\) de l’ensemble et l’observation cible \(\theta_t\).

3 Le challenge

3.1 Objectifs

L’objectif de ce travail est:

  • d’utiliser la méthode cohérente du BFS pour construire une prévision probabiliste à partir du modèle échangeable normal à deux pivots,

  • de montrer ses performances sur les données de températures de Vouglans, à diverses échéances.

  • de comparer ses performances à celles des méthodes courantes EMOS et BMA, par exemple.

  • de montrer comment intégrer dans le même schéma les informations issues d’autres systèmes d’ensembles.

3.2 Les performances à battre

La figure ci-après annonce l’objectif à battre en terme de CRPS par rapport à la méthode d’ensemble du CEP.

label<-data.frame(
  Echeance=c(8,5),
  CRPS= c(2.05,1.1),
  label=c("Climatologie","CEP")
)
d %>% filter(Year>2011) %>% group_by(Echeance) %>% mutate(crps_ens=crps(obs=Obs,pred = data.frame(Xbar,V2^0.5, na.rm=T))$crps,
                                    crps_clim=crps(obs=Obs,pred = data.frame(T_moy_ref_lis,T_std_ref_lis, na.rm=T))$crps) %>% summarize(CRPS_ens=mean(crps_ens,na.rm=T),CRPS_clim=mean(crps_clim,na.rm=T)) %>% 
  ggplot(aes(x=Echeance))+geom_line(aes(y=CRPS_clim),color='red')+geom_line(aes(y=CRPS_ens))+geom_point(aes(y=CRPS_clim),color='red')+geom_point(aes(y=CRPS_ens))+labs(y="CRPS")+geom_label(data=label,aes(y=CRPS, label=label))

On cherchera également à vérifier la calibration, par exemple en testant le caractère uniforme de leur probability integral transforms.

d %>% filter(Year>2011) %>%  mutate(pit_ens=crps(obs=Obs,pred = data.frame(Xbar,V2^0.5, na.rm=T))$pit,
                                    pit_clim=crps(obs=Obs,pred = data.frame(T_moy_ref_lis,T_std_ref_lis, na.rm=T))$pit) %>%
  ggplot()+geom_histogram(mapping=aes(x=pit_clim),stat="density", color='red')+
  geom_histogram(mapping=aes(x=pit_ens),stat="density",position = "dodge", color="blue", alpha=0.3)+geom_hline(yintercept = 1)+facet_wrap(~Echeance)+labs(x="Probability Integral Transform")
Ignoring unknown parameters: binwidth, bins, padIgnoring unknown parameters: binwidth, bins, pad

3.3 Diviser l’échantillon en apprentissage+validation

Pour tester la performance des diverses méthodes, on réalisera l’inférence sur un échantillon d’apprentissage (par exemple 2005-2008) et on validera sur l’échantillon restant (par exemple 2011-2015)

d %>% filter(Year < 2010, !is.na(Calendaire)) %>% 
  mutate(theta=(Obs-T_moy_ref_lis)/T_std_ref_lis,
         xbar=(Xbar-T_moy_ref_lis)/T_std_ref_lis,
         v2=  V2/(T_std_ref_lis^2) ) %>% 
   filter(!is.na(theta),
          !is.na(xbar),
          !is.na(v2)) -> 
  learning_sample
d %>% filter(Year > 2010 ,!is.na(Calendaire)) %>% 
  mutate(theta=(Obs-T_moy_ref_lis)/T_std_ref_lis,
         xbar=(Xbar-T_moy_ref_lis)/T_std_ref_lis,
         v2=  V2/(T_std_ref_lis^2) ) %>% 
 filter(!is.na(theta),
       !is.na(xbar),
       !is.na(v2)) -> 
  validation_sample

4 Modèles d’ensemble

4.1 Modèle Gamma-Normal à deux pivots

Rappelons la suggestion de Jacques dans son document EnsembleSaison. Les membres s’appuient sur deux pivots latents \(Z_{1t}\) et \(Z_{2t}\), de telle sorte que le modèle échangeable s’écrit:

\[{X_{ts}} = \alpha + \beta {Z_{1t}} + \lambda \sigma {Z_{2t}}^{ - O.5}{\varepsilon _{ts}}\]

\(\varepsilon _{ts}\sim N(0,1)\) tandis que \(\sigma\) peut être pris égal à \(1\). Les pivots latents sont munis de priors \[\begin{gathered} {Z_{2,t}} \sim Gamma(g,1) \hfill \\ {Z_{1t}}|{Z_{2t}} \sim N(0,{\sigma ^2}{Z_{2t}}^{ - 1}) \hfill \\ \end{gathered}\]

Travaillant avec les statistiques exhausives, on écrira \[\begin{gathered} {{\bar X}_t} \sim N(\alpha + \beta {Z_{1t}},\frac{{{\lambda ^2}{\sigma ^2}}}{{S{Z_{2t}}}}) \hfill \\ \sum\limits_{s = 1}^S {{{({X_{ts}} - {{\bar X}_t})}^2}} \sim \frac{{{\lambda ^2}{\sigma ^2}}}{{{Z_{2t}}}}{\chi ^2}(S - 1) \hfill \\ \end{gathered}\]

Soit encore, en posant \(V_{t}^{2}=\frac{{\sum\limits_{s = 1}^S {{{({X_{ts}} - {{\bar X}_t})}^2}} }}{{S - 1}}\), le modèle d’échantillonnage à \(t\), par orthogonalité des deux statistiques exhaustives moyenne et variances empiriques, se résume par les deux équations: \[\begin{gathered} {{\bar X}_t} \sim N(\alpha + \beta {Z_{1t}},\frac{{{\lambda ^2}{\sigma ^2}}}{{S{Z_{2t}}}}) \hfill \\ {V_{t}^{2}} \sim Gamma\left( {\frac{{S - 1}}{2},\frac{{{Z_{2t}}(S - 1)}}{{2{\lambda ^2}{\sigma ^2}}}} \right) \hfill \\ \end{gathered}\]

L’inférence des coefficients \(\alpha ,\beta ,\lambda , g\) sera menée sur l’ échantillon d’apprentissage. Prenons par exemple l’écheance à 4 jours des années 2005-2008.

learning_sample  %>% filter( Echeance == 4) %>%
                   dplyr::select(theta,xbar,v2) -> apprentissage

On procède à l’estimation bayésienne de \(\alpha ,\beta ,\lambda , g\) grâce au logiciel d’inférence bayésienne Jags. (A noter qu’on pourrait également utiliser STAN.)

model_string <- "
model{
a<-(S-1)/2
for (t in 1:N){
m[t] <- alpha+beta*Z1[t]
preci[t]<- Z2[t]/(lambda2)
xbar[t] ~ dnorm(m[t], preci[t])
b[t]<-preci[t]*a
v2[t] ~ dgamma(a,b[t])
# latentes
Z2[t] ~ dgamma(g,1)
Z1[t] ~ dnorm(0, 1)
}
alpha ~ dunif(-10,10)
beta ~ dunif(0,10)
lambda2 ~ dunif(0,10)
g ~ dunif(0.5,10)
}
"
params=c("alpha","beta","lambda2","g")
data <- list(xbar=apprentissage$xbar,
             v2=apprentissage$v2,
             N=length(apprentissage$xbar),
             S=50)
temp<-jags(data = data, model.file = textConnection(model_string),
           parameters.to.save = params,n.chains = 3,
           n.burnin = 5000,n.iter = 10000)
Compiling model graph
   Resolving undeclared variables
   Allocating nodes
Graph information:
   Observed stochastic nodes: 2908
   Unobserved stochastic nodes: 2912
   Total graph size: 11646

Initializing model


  |                                                        
  |                                                  |   0%
  |                                                        
  |++                                                |   4%
  |                                                        
  |++++                                              |   8%
  |                                                        
  |++++++                                            |  12%
  |                                                        
  |++++++++                                          |  16%
  |                                                        
  |++++++++++                                        |  20%
  |                                                        
  |++++++++++++                                      |  24%
  |                                                        
  |++++++++++++++                                    |  28%
  |                                                        
  |++++++++++++++++                                  |  32%
  |                                                        
  |++++++++++++++++++                                |  36%
  |                                                        
  |++++++++++++++++++++                              |  40%
  |                                                        
  |++++++++++++++++++++++                            |  44%
  |                                                        
  |++++++++++++++++++++++++                          |  48%
  |                                                        
  |++++++++++++++++++++++++++                        |  52%
  |                                                        
  |++++++++++++++++++++++++++++                      |  56%
  |                                                        
  |++++++++++++++++++++++++++++++                    |  60%
  |                                                        
  |++++++++++++++++++++++++++++++++                  |  64%
  |                                                        
  |++++++++++++++++++++++++++++++++++                |  68%
  |                                                        
  |++++++++++++++++++++++++++++++++++++              |  72%
  |                                                        
  |++++++++++++++++++++++++++++++++++++++            |  76%
  |                                                        
  |++++++++++++++++++++++++++++++++++++++++          |  80%
  |                                                        
  |++++++++++++++++++++++++++++++++++++++++++        |  84%
  |                                                        
  |++++++++++++++++++++++++++++++++++++++++++++      |  88%
  |                                                        
  |++++++++++++++++++++++++++++++++++++++++++++++    |  92%
  |                                                        
  |++++++++++++++++++++++++++++++++++++++++++++++++  |  96%
  |                                                        
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100%

  |                                                        
  |                                                  |   0%
  |                                                        
  |**                                                |   4%
  |                                                        
  |****                                              |   8%
  |                                                        
  |******                                            |  12%
  |                                                        
  |********                                          |  16%
  |                                                        
  |**********                                        |  20%
  |                                                        
  |************                                      |  24%
  |                                                        
  |**************                                    |  28%
  |                                                        
  |****************                                  |  32%
  |                                                        
  |******************                                |  36%
  |                                                        
  |********************                              |  40%
  |                                                        
  |**********************                            |  44%
  |                                                        
  |************************                          |  48%
  |                                                        
  |**************************                        |  52%
  |                                                        
  |****************************                      |  56%
  |                                                        
  |******************************                    |  60%
  |                                                        
  |********************************                  |  64%
  |                                                        
  |**********************************                |  68%
  |                                                        
  |************************************              |  72%
  |                                                        
  |**************************************            |  76%
  |                                                        
  |****************************************          |  80%
  |                                                        
  |******************************************        |  84%
  |                                                        
  |********************************************      |  88%
  |                                                        
  |**********************************************    |  92%
  |                                                        
  |************************************************  |  96%
  |                                                        
  |**************************************************| 100%
temp$BUGSoutput$sims.matrix %>% 
  as.data.frame() %>% 
  dplyr::select(alpha,beta,lambda2,g) ->alphabetalambda2g
  alphabetalambda2g %>% 
  gather(param, value) %>% 
  ggplot(aes_string(x="value")) +
  geom_density(alpha=0.5) +
  facet_grid(param~.,scales = "free") 

L’intercept \(\alpha\) est possiblement nul, la pente \(\beta\) restant inférieure à \(1\). La connaissance a posteriori de \(g\) est assez incertaine; \(\lambda^2\) et \(g\) sont fortement corrélés.

ggpairs(data = alphabetalambda2g)

knitr::kable(rbind(mean=apply(X = alphabetalambda2g,2,mean),std=apply(X = alphabetalambda2g,2,var)^0.5,apply(X = alphabetalambda2g,2, quantile, probs=c(0.05,0.25,0.5, 0.75, 0.95))), dig=3)
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio

alpha beta lambda2 g
mean 0.023 0.904 0.097 1.778
std 0.025 0.018 0.004 0.063
5% -0.018 0.875 0.090 1.676
25% 0.006 0.891 0.094 1.735
50% 0.023 0.903 0.097 1.777
75% 0.039 0.916 0.100 1.821
95% 0.064 0.933 0.104 1.880

knitr::kable(cor(alphabetalambda2g),dig=3)
alpha beta lambda2 g
alpha 1.000 -0.108 -0.022 -0.016
beta -0.108 1.000 0.087 0.082
lambda2 -0.022 0.087 1.000 0.871
g -0.016 0.082 0.871 1.000

4.2 Analyse marginale du modèle à deux pivots

La vraisemblance à \(t\) s’écrit: \[\begin{gathered} {L_t} = [{{\bar X}_t},{V_t}^2|{Z_{1t}},{Z_2}] \hfill \\ {L_t} \propto {Z_2}^{\frac{1}{2}}\exp - \frac{{{Z_{2t}}S}}{{2{\lambda ^2}}}\left\{ {{{\left( {{{\bar X}_t} - \alpha - \beta {Z_{1t}}} \right)}^2}} \right\} \times {Z_{2t}}^{\frac{S}{2} - \frac{1}{2}}\exp - \frac{{{Z_{2t}}}}{{2{\lambda ^2}}}\left\{ {\left( {S - 1} \right){V_t}^2} \right\} \hfill \\ \end{gathered}\]

On va travailler comme Krzysztofowicz en négligeant les incertitudes des hyper-paramètres du modèle marginal.

alpha<-mean(alphabetalambda2g[,"alpha"])
beta<-mean(alphabetalambda2g[,"beta"])
lambda2<-mean(alphabetalambda2g[,"lambda2"])
g<-mean(alphabetalambda2g[,"g"])
S<-50
Repet <-1000
data.frame(Z2=rgamma(Repet,g,1), Z1=rnorm(Repet)) %>% 
  mutate(xbar=alpha+beta*Z1+sqrt(lambda2/Z2/S)*rnorm(Repet),
  v2=lambda2/Z2/(S-1)*rchisq(Repet,df=S-1)) %>% filter(v2<2) %>% 
  dplyr::select(xbar,v2) %>% gather(key,value) %>% ggplot(aes(x=value,color=key))+
  geom_histogram(data= apprentissage %>% dplyr::select(xbar,v2) %>% filter(v2<2) %>%  gather(key,value), stat = "density")+
  geom_freqpoly(stat = "density", color='black')+facet_wrap(~key,scales="free")
Ignoring unknown parameters: binwidth, bins, pad

Sur cette comparaison estimation empirique et modèle, on voit que les lois marginales se calent raisonnablement avec notre modèle gamma-normal echangeable, malgré que nou sn’ayons pas tenu compte des incertitudes à propos des hyper-paramètres (\(\alpha,\beta, \gamma^2, g\)) du modèle marginal.

4.3 Analyse préquentielle du modèle à deux pivots

4.3.1 Expression de la conditionnelle des deux variables pivots latentes

La vraisemblance complète quant à elle est légèrement plus compliquée: \[\begin{gathered} C{L_t} = [{{\bar X}_t},{V_t}^2,{Z_{1t}},{Z_2}] = [{{\bar X}_t},{V_t}^2|{Z_{1t}},{Z_{2t}}] \times [{Z_{1t}},{Z_{2t}}] \hfill \\ C{L_t} \propto {L_t} \times \left( {{Z_2}^{\frac{1}{2}}\exp - \frac{{{Z_{2t}}}}{2}Z_{1t}^2} \right) \times \left( {Z_{2t}^{g - 1}\exp - {Z_{2t}}} \right) \hfill \\ \end{gathered}\]

En développant: \[\begin{gathered} C{L_t} \propto \left( {{Z_2}^{\frac{1}{2}}\exp - \frac{{{Z_{2t}}}}{2}\left\{ {Z_{1t}^2 + \frac{{S{\beta ^2}}}{{{\lambda ^2}}}{{\left( {{Z_{1t}} - \frac{{{{\bar X}_t} - \alpha }}{\beta }} \right)}^2}} \right\}} \right) \hfill \\ \times \left( {Z_{2t}^{g + \frac{S}{2} - 1}\exp - {Z_{2t}}(1 + \frac{{\left( {S - 1} \right){V_t}^2}}{{2{\lambda ^2}}})} \right) \hfill \\ \end{gathered}\]

puis en regroupant:

\[\begin{gathered} Z_{1t}^2 + \frac{{S{\beta ^2}}}{{{\lambda ^2}}}{\left( {{Z_{1t}} - \frac{{{{\bar X}_t} - \alpha }}{\beta }} \right)^2} = \left( {\frac{{S{\beta ^2} + {\lambda ^2}}}{{{\lambda ^2}}}} \right){\left( {Z_{1t}^{} - \left( {\frac{{{{\bar X}_t} - \alpha }}{\beta }} \right)\frac{{S{\beta ^2}}}{{S{\beta ^2} + {\lambda ^2}}}} \right)^2} \hfill \\ + \left\{ {\left( {\frac{{S{{\left( {{{\bar X}_t} - \alpha } \right)}^2}}}{{S{\beta ^2} + {\lambda ^2}}}} \right)} \right\} \hfill \\ \end{gathered}\]

la conjugaison adhoc permet d’obtenir les conditionnelles a posteriori

\[\begin{gathered} {Z_{2t}}|{{\bar X}_t},{V_t}^2 \sim Gamma(g + \frac{S}{2},1 + \frac{{\left( {S - 1} \right){V_t}^2}}{{2{\lambda ^2}}} + \left( {\frac{{S{{\left( {{{\bar X}_t} - \alpha } \right)}^2}}}{{2\left( {S{\beta ^2} + {\lambda ^2}} \right)}}} \right)) \hfill \\ {Z_{1t}}|{Z_{2t}},{{\bar X}_t},{V_t}^2 \sim N(\left( {\frac{{{{\bar X}_t} - \alpha }}{\beta }} \right)\frac{{S{\beta ^2}}}{{S{\beta ^2} + {\lambda ^2}}},{\left\{ {\left( {\frac{{S{\beta ^2} + {\lambda ^2}}}{{{\lambda ^2}}}} \right){Z_{2t}}} \right\}^{ - 1}}) \hfill \\ \end{gathered}\]

4.3.1.1 Nota:

Je pense qu’ il y a une erreur typographique dans le papier de Jacques en page 8 pour le calcul de l’espérance de \(Z_1\).

4.3.2 Apprentissage d’un lien des deux variables pivots latentes avec la variable cible

Pour chaque situation de la base d’apprentissage, nous générons un \(Z_1(t)\) que nous mettons en regard avec la cible.

La suggestion de Jacques est de repasser en marginale et de travailler avec les QNT:

  • la cible qui a été centrée réduite est déjà N(0,1).

  • \(Z_1\) est marginalement Student à \(2g\) degrés de liberté. Quant à \(g^{0.5} \times Z_1\), il est Student unitaire à \(2g\) degrés de liberté.

lm(QNT_Z1~theta-1, data=apprentissage)$coef
    theta 
0.8803137 

Sur l’échantillon d’apprentissage, on trouve un coefficient de corrélation \(\hat{\rho}=0.8803\) entre le QNT de la cible et le QNT d’une valeur tirée au hasard dans la loi conditionnelle du premier pivot sachant les deux statistiques résumant l’ensemble échangeable.

4.4 Analyse prédictive grâce au modèle à deux pivots

Considérons maintenant l’échantillon de validation ou l’on va appliquer le calcul prédictif:

\([\theta|X]=\int_{QNT} [\theta|QNT] \times [QNT|X] \times dQNT\)

Et si l’on dispose d’un prior informatif \([\theta]\) pour la cible:

\[[\theta|X]=\int_{QNT} \frac{[QNT|\theta]\times [\theta]}{[QNT]} \times [QNT|X] \times dQNT\] Si on utilise la conjugaison normale avec un prior \(N(m_t,s_t^2)\) pour \([\theta_t]\), \([\theta|QNT]\) est \(N(m'_t,s'_t^2)\) avec

  • \(\frac{1}{s'^2}=\frac{1}{s^2}+\frac{\rho^2}{1-\rho^2}\)

  • \(\frac{m'}{s'^2}=\frac{m}{s^2}+\frac{QNT}{\rho}\frac{\rho^2}{1-\rho^2}\)

pour \(m=0, s=1\), c’est à dire pour un prior climatologique pour produire une prévision probabiliste calibrée, on retrouve les résultats de la régression \([\theta|QNT]\) est \(N(\rho \times QNT,1-\rho^2)\)

validation_sample %>% filter(Echeance==4) %>% mutate(a_Z2=g+S/2,                       b_Z2=1+(S-1)*v2/2/lambda2+S*((xbar-alpha)^2)/2/(S*beta^2+lambda2),
m_Z1=   (xbar-alpha)*S*beta /(S*beta^2+lambda2) ,
precision_Z1=(S*beta^2+lambda2)/lambda2) ->validation 

4.5 EMOS

Je veux comparer à un modèle bayésien qui conditionne les valeurs de l’ensemble sur la température vraie que j’ai appellé \(\theta\) car c’ est la notation conventionnelle de l’état de la nature d’un problème statistique. Je propose : \[ \theta \sim N(0,1) \\ \bar{X}\vert (V^2,\theta) \sim N(a\times \theta +b , c+ d\times V^2) \\ log(V^2) \sim N(g,s^2) \]

L’inférence des coefficients \(a,b,c,d,g,s^2\)) sera menée sur un échantillon d’apprentissage.

La loi prédictive (c-à-d conditionnelle sachant les résumés de l’ensemble \(\bar{X}\) et \(V^2\)) est ici “immédiate”, par conjugaison normale:

\[\theta \vert (\bar{X},V^2,a,b,c,d) \sim N\left\{ {\left( {\frac{{\bar X}}{a} - b} \right)\frac{{{a^2}}}{{{a^2} + c + d{V^2}}},\frac{{c + d{V^2}}}{{{a^2} + c + d{V^2}}}} \right\}\]

Ses performances seront analysées sur un échantillon de validation. Prenons par exemple l’écheance à 4 jours des années 2005-2008.

d %>% filter(Year < 2010, Echeance == 4, !is.na(Calendaire)) %>% 
  mutate(theta=(Obs-T_moy_ref_lis)/T_std_ref_lis,
         xbar=(Xbar-T_moy_ref_lis)/T_std_ref_lis,
         v2=  V2/(T_std_ref_lis^2) ) %>% 
  dplyr::select(theta,xbar,v2) %>% filter(!is.na(theta),
                                   !is.na(xbar),
                                   !is.na(v2)) -> 
  learning_sample

On va réaliser l’inférence bayésienne des coefficients \(a,b,c,d\)

model_string <- "
model{
for (i in 1:N){
m[i] <- a*theta[i]+b
preci[i]<- 1/(c+d*v2[i])
xbar[i] ~ dnorm(m[i], preci[i])
}
a ~ dunif(-10,10)
b ~ dunif(-10,10)
c ~ dunif(0,10)
d ~ dunif(0,10)
}
"
params=c("a","b","c","d")
data <- list(theta=learning_sample$theta,
             xbar=learning_sample$xbar,
             v2=learning_sample$v2,
             N=length(learning_sample$theta))
temp<-jags(data = data, model.file = textConnection(model_string),
           parameters.to.save = params,n.chains = 3,
           n.burnin = 500,n.iter = 1000)
temp$BUGSoutput$sims.matrix %>% 
  as.data.frame() %>% dplyr::select(a,b,c,d) ->abcd
  abcd %>% gather(param, value) %>% 
  ggplot(aes_string(x="value")) +
  geom_density(alpha=0.5) +
  facet_grid(param~.,scales = "free_x") 

Les coefficients \(a,b,c\) sont assez précisément estimés sauf la valeur de \(d\) qui est plus incertaine, tandis que les coefficients \(c\) et \(d\) sont anti-corrélés.

knitr::kable(rbind(mean=apply(X = abcd,2,mean),std=apply(X = abcd,2,var)^0.5,apply(X = abcd,2, quantile, probs=c(0.05,0.25,0.5, 0.75, 0.95))), dig=3)
knitr::kable(cor(abcd),dig=3)

4.6 Predictive bayésienne

Sur un échantillon de validation (ici 2011-2015), on va étudier le comportement du prédicteur bayésien. \[\theta \vert (\bar{X},V^2,a,b,c,d) \sim N\left\{ {\left( {\frac{{\bar X}-b}{a}} \right)\frac{{{a^2}}}{{{a^2} + c + d{V^2}}},\frac{{c + d{V^2}}}{{{a^2} + c + d{V^2}}}} \right\}\]

d %>% filter(Year > 2010, Echeance == 4, !is.na(Calendaire)) %>% 
  mutate(theta=(Obs-T_moy_ref_lis)/T_std_ref_lis,
         xbar=(Xbar-T_moy_ref_lis)/T_std_ref_lis,
         v2=  V2/(T_std_ref_lis^2) ) %>% 
  filter(!is.na(theta),!is.na(xbar),!is.na(v2)) -> 
  validation_sample
indices<-sample(x = 1:dim(abcd)[1], size=50, rep=F)
A=abcd[indices,1]
B=abcd[indices,2]
m=(outer(validation_sample$xbar,A,'/')-B/A)
C=abcd[indices,3]
D=abcd[indices,4]
s=outer(validation_sample$v2,D,'*')+C
m=m*A*A/(A^2+s)
s=(s/s+A*A)^0.5
YPRED=matrix(
  rnorm(n = prod(dim(s)), mean = m, sd = s),
  nr=dim(s)[1],nc=dim(s)[2] )*
  validation_sample$T_std_ref_lis+validation_sample$T_moy_ref_lis
moyPRED=apply(YPRED, 1, mean)
stdPRED=apply(YPRED, 1, sd)
rm(YPRED)
LS0tCnRpdGxlOiAiTW9kw6hsZSBkJ2Vuc2VtYmxlcyDDqWNoYW5nZWFibGUiCnN1YnRpdGxlOiAiIFVuIEV4ZW1wbGUgZGUgVGVtcMOpcmF0dXJlcyBwb3VyIGwnYXBwcm9jaGUgbm9ybWFsZSIKYXV0aG9yOiBFcmljIFBhcmVudApkYXRlOiAiMTcganVpbGxldCAyMDE5IgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAyCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwotLS0KIyBJbnRyb2R1Y3Rpb24KCkphY3F1ZXMgZXQgbW9pIGF2b25zIGJlYXVjb3VwIGNvbW11bmlxdcOpIHN1ciBsZXMgbW9kw6hsZXMgZCdlbnNlbWJsZSDDqWNoYW5nZWFibGVzLCBtYWlzIGFic29sdW1lbnQgcmllbiB2YWxvcmlzw6kgZW4gdGVybWUgZCdhcnRpY2xlIHZpcyDDoCB2aXMgZGUgbGEgY29tbXVuYXV0w6kgZCdoeWRyb2xvZ2llIHN0YXRpc3RpcXVlIHF1ZSBub3VzIG5vdXMgcGxhaXNvbnMgdGFudCDDoCBjcml0aXF1ZXIuIApEJ2F1dHJlIHBhcnQsIG5vcyBhY2NvcmRzIChhaW5zaSBxdWUgZMOpc2FjY29yZHMpIHRow6lvcmlxdWVzIHN1ciBsJ2ludMOpcsOqdCBkdSBjYXJhY3TDqHJlIMOpY2hhbmdlYWJsZSBwb3VyIG1vZMOpbGlzZXIgbGVzIGVuc2VtYmxlcyBldCBkdSBCYXllc2lhbiBGb3JlY2FzdGluZyBTeXN0ZW0gZGUgS3J6eXN6dG9mb3dpY3ogYWZpbiBkZSBjb25zdHJ1aXJlIHVuZSBwcsOpZGljdGl2ZSByZXN0ZXJvbnQgZHUgZG9tYWluZSBkZXMgcGxhaXNhbnRlcyBqb3V0ZXMgZXBpc3RvbGFpcmVzIGxpbWl0w6llcyDDoCBkZXV4IGNyb2NvZGlsZXMgYmF5w6lzaWVucyBzaSBub3VzIG5lIG1vbnRyb25zIHBhcyBsZXVyIGNhcmFjdMOocmUgb3DDqXJhdG9pcmUgc3VyIGRlICJ2cmFpZXMiIGpldXggZGUgZG9ubsOpZXMuIEplIHByb3Bvc2UgZG9uYyBkZSB0cmFpdGVyIGRlIHRlbHMgamV1eCBkZSBkb25uw6llcyBwb3VyIGxlc3F1ZWxzIG9uIG1ldHRyYSBlbiDDqXZpZGVuY2UgbGVzIGFtw6lsaW9yYXRpb25zIGNvbmNyw6h0ZXMgZXQgcHJhdGlxdWVzIHF1ZSBjZXMgbcOpdGhvZGVzIGFwcG9ydGVudCAoYXUgZGVsw6AgZGUgbGV1ciBjYXJhY3TDqHJlIHRow6lvcmlxdWUgcmF0aW9uZWwgZXQgY29ow6lyZW50KS4KQydlc3QgbCdvYmplY3RpZiBkZSBjZSBwYXBpZXIsIMOpY3JpdCBlbiAqTWFya2Rvd24gaHRtbCBub3RlYm9vayogZGFucyBsJ2ludGVudGlvbiBkZSBkb25uZXIgdW5lIGlsbHVzdHJhdGlvbiBpbmZvcm1hdGlxdWUgZXQgYXBwbGljYXRpdmUgKmVuIG3Dqm1lIHRlbXBzKiBxdWUgZHUgdGV4dGUgcGx1cyB0aMOpb3JpcXVlLgpKJ2ltYWdpbmUgZW4gZmFpdCBkZXV4IHBhcGllcnMgZGUgY2UgdHlwZToKCiogbGUgcHJlbWllciBpY2kgc3VyIGxlIG1vZMOobGUgbm9ybWFsIMOpY2hhbmdlYWJsZSBpbnNwaXLDqSBkdSBtb2TDqGxlIMOgIGRldXggcGl2b3RzICh2b2lyIHBvdXIgbGUgKmp1cyB0aMOpb3JpcXVlKiBsZSB0ZXh0ZSBkZSBKYWNxdWVzICpFTlNFTUJMRVNBSVNPTi50ZXgqKSBxdWUgbCdvbiBwZXV0IGlsbHVzdHJlciBzdXIgZGVzIGRvbm7DqWVzIGRlIHRlbXDDqXJhdHVyZXMgZCdFREYtRFRHLCAgCgoqIGxlIHNlY29uZCwgw6AgdmVuaXIsIHN1ciBkZXMgZG9ubsOpZXMgesOpcm9zLWluZmxhdMOpZXMgZGUgcHLDqWNpcGl0YXRpb25zLCBvw7kgbCdvbiBtZXR0cmFpdCBlbiBvZXV2cmUgbGVzIG1vZMOobGVzIEJlcm5vdWxsaS1HYW1tYSBldCBsb2lzIGRlcyBmdWl0ZXMuIENlcyBwYXBpZXJzIHBvdXJyb250IHNlcnZpciBkZSBiYXNlIMOgIGRlcyBhcnRpY2xlcyBwdWJsaWFibGVzIMOgIHRlcm1lLi4uIHF1ZSBqJ2VzcMOocmUgcGFzIHRyw6hzIGxvaW50YWluLgoKIyBBbmFseXNlcyBEZXNjcmlwdGl2ZXMKCiMjIExlcyBkb25uw6llcyBkaXNwb25pYmxlcwpgYGB7cn0Kcm0obGlzdD1scygpKQojIHNldHdkKCJ+L0RvY3VtZW50cy9jb3VyYmFyaWF1eC9XT1JLTFVDL1RlbXBFbnNlbWJsZXNOb3JtYWwiKQpsb2FkKCJBaW5AVm91Z2xhbnMuUmRhdGEiKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5c2VsZWN0KQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShSMmphZ3MpCmxpYnJhcnkodmVyaWZpY2F0aW9uKQpsaWJyYXJ5KEdHYWxseSkKYGBgCgpTdWl0ZSDDoCBsYSB0aMOoc2UgZGUgTWFyaWUgQ291cmJhcmlhdXgsIGplIGRpc3Bvc2UgZGVzIGRvbm7DqWVzIGpvdXJuYWxpw6hyZXMgZGUgdGVtcMOpcmF0dXJlcyBmb3VybmllcyBwYXIgRURGLURURyBhdXggc3RhdGlvbnMgc3VpdmFudGVzOgoKLSBsJyBBaW4gw6AgVm91Z2xhbnMsIAoKLSBsZSBCdWVjaCDDoCBDaGFtYm9ucywKCi0gbGUgRHJhYyDDoCBTYXV0ZXQuCgpKZSBwcm9wb3NlIGRlIHRyYXZhaWxsZXIgZGFucyB0b3V0ZSBsYSBzdWl0ZSBzdXIgbCdBaW4gw6AgVm91Z2xhbnMsIG1haXMgbGVzIHByb2dyYW1tZXMgZMOpdmVsb3Bww6lzIHNvbnQgZGlyZWN0ZW1lbnQgdHJhbnNwb3NhYmxlcyBhdXggZGV1eCBhdXRyZXMgc3RhdGlvbnMuIE5vdXMgZGlzcG9zb25zIGRlIGwnaGlzdG9yaXF1ZSBqb3VybmFsaWVyIHN1ciBsYSBww6lyaW9kZSAxOTUzIMOgIDIwMTUgZGFucyBsZSAqZGF0YS5mcmFtZSBkaGlzdG8qLCBhaW5zaSBxdWUgZGVzICQ1MCQgbWVtYnJlcyBkdSBDZW50cmUgRXVyb3DDqWVuIGRlIFByw6l2aXNpb24gcG91ciBsZXMgYW5uw6llcyAyMDA1IMOgIDIwMDggKDRhbnMpIHB1aXMgZGUgMk8xMSDDoCAxMDE1ICg1IGFucykgcG91ciBkZXMgcHLDqXZpc2lvbnMganVzcXUnw6AgdW5lIMOpY2jDqWFuY2UgZGUgOSBqb3VycyBkYW5zIGxlICpkYXRhLmZyYW1lIGR0b3V0Ki4KCgpgYGB7cn0KZGhpc3RvICU+JSBnbGltcHNlCnJhbmdlKGRoaXN0byREYXRlKQpkdG91dCAlPiUgaGVhZCgpCnVuaXF1ZShkdG91dCRZZWFyKQp1bmlxdWUoZHRvdXQkRWNoZWFuY2UpCmBgYAoKIyMgQW5hbHlzZSBjbGltYXRvbG9naXF1ZQoKT24gcGV1dCBpbGx1c3RyZXIgbGVzIGZsdWN0dWF0aW9ucyBzYWlzb25uacOocmVzIGRlcyB0ZW1ww6lyYXR1cmVzIGpvdXJuYWxpw6hyZXMgYXUgY291cnMgZGUgbCdhbm7DqWU6CgpgYGB7cn0KZGhpc3RvICU+JSBtdXRhdGUoYW49IGFzX2ZhY3RvcihZZWFyKSkgJT4lIGdncGxvdChhZXMoeCA9IENhbGVuZGFpcmUsIHk9IFQsIGNvbG9yID0gYW4pKSArIGdlb21fbGluZShhbHBoYT0wLjUpCmhvcnNhaW4gPSB5bWQoIjIwMDMtMTAtMTUiKQpgYGAKCkNlY2kgbWV0IGVuIMOpdmlkZW5jZSB1biBjb21wb3J0ZW1lbnQgdHLDqHMgc8O7cmVtZW50IGFiZXJyYW50IGVuIGRhdGUgZHUgMjAwMy0xMC0xNSwgaG9yc2FpbiBxdWUgbCdvbiBlbmzDqHZlCmBgYHtyfQpkaGlzdG8gJT4lIGZpbHRlcihEYXRlIT0gaG9yc2FpbikgJT4lIG11dGF0ZShhbj0gYXNfZmFjdG9yKFllYXIpKSAlPiUgZ2dwbG90KGFlcyh4ID0gQ2FsZW5kYWlyZSwgeT0gVCkpICsgZ2VvbV9wb2ludChhZXMoIGNvbG9yID0gYW4pLGFscGhhPTAuNSwgc2hhcGUgPScuJywgc2hvdy5sZWdlbmQgPSBGQUxTRSkrZ2VvbV9zbW9vdGgoKStsYWJzKHg9J0pvdXJzIENhbGVuZGFpcmVzJywgeT0nVGVtcMOpcmF0dXJlcycsIHRpdGxlPSJDbGltYXRvbG9naWUgw6AgVm91Z2xhbnMiKQpgYGAKCk9uIHZhIGFsb3JzIGNvbnN0cnVpcmUgdW5lIHRlbXDDqXJhdHVyZSBtb3llbm5lIGRlIHLDqWZlcmVuY2UgZXQgc29uIMOpY2FydC10eXBlIGFzc29jacOpLCDDqXZlbnR1ZWxsZW1lbnQgbGlzc8OpcyBwb3VyIGNoYXF1ZSBqb3VyIGNhbGVuZGFpcmUgZGUgbCdhbm7DqWUuIFBvdXIgY29uc3RydWlyZSBjZXMgZXN0aW1hdGV1cnMgcMOpcmlvZGlxdWVzLCBvbiAqcsOpZ3Jlc3NlKiBzdXJlIHVuZSBiYXNlIGRlcyA0IHByZW1pw6hyZXMgaGFybW9uaXF1ZXMgbGEgdGVtcMOpcmF0dXJlIGludGVyYW5udWVsbGUgbW95ZW5uZSBldCBsZSBsb2dhcml0aG1lIGRlIGwnw6ljYXJ0LXR5cGUgam91cm5hbGllciBkZSBsYSB0ZW1ww6lyYXR1cmUuIAoKYGBge3J9CmRoaXN0byAlPiUgZmlsdGVyKERhdGUhPSBob3JzYWluLCBDYWxlbmRhaXJlICE9IDM2NikgJT4lIG11dGF0ZShhbj0gYXNfZmFjdG9yKFllYXIpKSAlPiUgZ3JvdXBfYnkoQ2FsZW5kYWlyZSkgJT4lIHN1bW1hcml6ZSAoVF9tb3lfcmVmPSBtZWFuKFQpLFRfc3RkX3JlZj0gdmFyKFQpXjAuNSApIC0+IFRfcmVmCgptb2RsaW48LWxtKFRfbW95X3JlZn4gMSsgc2luKDIqcGkqQ2FsZW5kYWlyZS8zNjUpKyAgICAgICAgICAgICAgICAgICAgICAgIGNvcygyKnBpKkNhbGVuZGFpcmUvMzY1KSsKICAgICAgICAgIHNpbig0KnBpKkNhbGVuZGFpcmUvMzY1KSArCiAgICAgICAgICBjb3MoNCpwaSpDYWxlbmRhaXJlLzM2NSkrCiAgICAgICAgICBzaW4oNipwaSpDYWxlbmRhaXJlLzM2NSkgKwogICAgICAgICAgY29zKDYqcGkqQ2FsZW5kYWlyZS8zNjUpKwogICAgICAgICAgc2luKDgqcGkqQ2FsZW5kYWlyZS8zNjUpKwogICAgICAgICAgY29zKDgqcGkqQ2FsZW5kYWlyZS8zNjUpLCBkYXRhPVRfcmVmCiAgICAgICAgICApCm1vZGxpblN0ZCA8LWxtKGxvZyhUX3N0ZF9yZWYpfiAxKyBzaW4oMipwaSpDYWxlbmRhaXJlLzM2NSkrICAgICAgICAgICAgICAgICAgICAgICAgY29zKDIqcGkqQ2FsZW5kYWlyZS8zNjUpKwogICAgICAgICAgc2luKDQqcGkqQ2FsZW5kYWlyZS8zNjUpICsKICAgICAgICAgIGNvcyg0KnBpKkNhbGVuZGFpcmUvMzY1KSsKICAgICAgICAgIHNpbig2KnBpKkNhbGVuZGFpcmUvMzY1KSArCiAgICAgICAgICBjb3MoNipwaSpDYWxlbmRhaXJlLzM2NSkrCiAgICAgICAgICBzaW4oOCpwaSpDYWxlbmRhaXJlLzM2NSkrCiAgICAgICAgICBjb3MoOCpwaSpDYWxlbmRhaXJlLzM2NSksIGRhdGE9VF9yZWYKICAgICAgICAgICkKClRfcmVmICU+JSBtdXRhdGUoVF9tb3lfcmVmX2xpcz1tb2RsaW4kZml0dGVkLnZhbHVlcywKICAgICAgICAgICAgICAgICBUX3N0ZF9yZWZfbGlzPWV4cChtb2RsaW5TdGQkZml0dGVkLnZhbHVlcykgKS0+IFRfcmVmClRfcmVmJT4lIAogIGdncGxvdChhZXMoeD1DYWxlbmRhaXJlKSkrZ2VvbV9saW5lKGFlcyh5PVRfbW95X3JlZl9saXMpKSArIGdlb21fcG9pbnQoYWVzKHk9VF9tb3lfcmVmKSkrbGFicyhjYXB0aW9uPSJNb3llbm5lIGNhbGVuZGFpcmUgKHLDqWbDqXJlbmNlIGNsaW1hdG9sb2dpcXVlKSIpClRfcmVmICU+JSAgCiAgZ2dwbG90KGFlcyh4PUNhbGVuZGFpcmUpKStnZW9tX2xpbmUoYWVzKHk9VF9zdGRfcmVmX2xpcykpICsgICAgZ2VvbV9wb2ludChhZXMoeT1UX3N0ZF9yZWYpKStsYWJzKGNhcHRpb249IkVjYXJ0LVR5cGUgY2FsZW5kYWlyZSAocsOpZsOpcmVuY2UgY2xpbWF0b2xvZ2lxdWUpICIpCmBgYAoKCiMjIEFuYWx5c2UgZGVzY3JpcHRpdmUgZGUgbCdlbnNlbWJsZSBkdSBjZW50cmUgZXVyb3DDqWVuIGRlIHByw6l2aXNpb24KCkwnYW5hbHlzZSBjbGltYXRvbG9naXF1ZSBwcsOpY8OpZGVudGUgcGVybWV0IGRlIGNlbnRyZXIgZXQgcsOpZHVpcmUgbGVzIHRlbXDDqXJhdHVyZXMgcG91ciB0cmF2YWlsbGVyIGVuICphbm9tYWxpZXMqIGpvdXJuYWxpw6hyZXMuCkNlcyBhbm9tYWxpZXMgc2FucyBkaW1lbnNpb24gc29udCBhbG9ycyBjb25zaWTDqXLDqWVzIGNvbW1lIGdhdXNzaWVubmVzLCBtYXJnaW5hbGVtZW50ICROKDAsMSkkLiBPbiB2YSBsYWlzc2VyIHRvbWJlciBsZXMgbWVtYnJlcyBpbmRpdmlkdWVscyBwb3VyIGVuIGNvbnNlcnZlciBsZXMgc3RhdGlzdGlxdWVzIGV4aGF1c3RpdmVzLCBtb3llbm5lIGV0IHZhcmlhbmNlIGVtcGlyaXF1ZXMgZGFucyBsZSBkYXRhZnJhbWUgZGUgdHJhdmFpbCAqZCouCkNvbnN0cnVpc29ucyBkJ2Fib3JkIHVuZSBiYXNlIGRlIGRvbm7DqWVzIChlbiBham91dGFudCBsYSBjbGltYXRvbG9naWUpLgpgYGB7cn0KZHRvdXQgJT4lICBtdXRhdGUoWGJhcj1yb3dNZWFucyhkcGx5cjo6c2VsZWN0KC4sc3RhcnRzX3dpdGgoIlJ1biIpKSksCiAgICAgICAgICAgICAgICAgIFYyPWFwcGx5KGRwbHlyOjpzZWxlY3QoLixzdGFydHNfd2l0aCgiUnVuIikpLDEsdmFyKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtc3RhcnRzX3dpdGgoIlJ1biIpKSAtPmQKYGBgCgoKIyMjIFJlbGF0aW9uIGVudHJlIG1veWVubmUgZGUgbCdlbnNlbWJsZSBldCB0ZW1ww6lyYXR1cmUgb2JzZXJ2w6llCgogUmVwcsOpc2VudG9ucyBlbiBwcmVtaWVyIGxpZXUgbGUgbGllbiBlbnRyZSB0ZW1ww6lyYXR1cmUgb2JzZXJ2w6llIGV0IHRlbXDDqXJhdHVyZSBtb3llbm5lIHByw6l2dWUgw6AgZGl2ZXJzZXMgw6ljaMOpYW5jZXMgKGFwcsOocyBjZW50cmFnZSBwYXIgbGEgbW95ZW5uZSBjbGltYXRvbG9naXF1ZSBjYWxlbmRhaXJlIGV0IHLDqWR1Y3Rpb24gcGFyIGwnw6ljYXJ0LXR5cGUgY2xpbWF0b2xvZ2lxdWUgY2FsZW5kYWlyZSAgKSAKCmBgYHtyfQogZCAlPiUgbGVmdF9qb2luKFRfcmVmICU+JSBkcGx5cjo6c2VsZWN0KC1UX3N0ZF9yZWYsLVRfbW95X3JlZikpIC0+ZAogZCAlPiUgZ2dwbG90KGFlcyh4PShPYnMtVF9tb3lfcmVmX2xpcykvVF9zdGRfcmVmX2xpcyx5PShYYmFyLVRfbW95X3JlZl9saXMpL1Rfc3RkX3JlZl9saXMsY29sb3I9YXMuZmFjdG9yKE1vbnRoKSkgKStnZW9tX3BvaW50KCkrZ2VvbV9hYmxpbmUoc2xvcGU9MSxpbnRlcmNlcHQ9MCkgK2ZhY2V0X3dyYXAofkVjaGVhbmNlKStsYWJzKHg9IkFub21hbGllIGRlIHRlbXDDqXJhdHVyZSIsIHk9IkFub21hbGllIGRlIHByw6l2aXNpb24iLCBjb2xvcj0iTW9pcyIpCgpgYGAKClBvdXIgbGEgbW95ZW5uZSBkJ2Vuc2VtYmxlLCBvbiBjb25zdGF0ZSBxdWUgbGUgYmlhaXMgZXQgbGEgZGlzcGVyc2lvbiB2aXMgw6AgdmlzIGRlIGxhIGNpYmxlIGF1Z21lbnRlbnQgYXZlYyBsJ8OpY2jDqWFuY2UsIGNvbW1lIG9uIHMneSBhdHRlbmQuCmBgYHtyfQpkICU+JSBtdXRhdGUoeD0oT2JzLVRfbW95X3JlZl9saXMpL1Rfc3RkX3JlZl9saXMseT0oWGJhci1UX21veV9yZWZfbGlzKS9UX3N0ZF9yZWZfbGlzKSAlPiUgZ3JvdXBfYnkoRWNoZWFuY2UpICU+JSBzdW1tYXJpc2UoYmlhaXM9bWVhbih4LXksIG5hLnJtPVQpLCB2YXJpYW5jZT12YXIoeC15LCBuYS5ybT1UKSwgc2NlPW1lYW4oKHgteSleMiwgbmEucm09VCkpCmBgYApQb3VyIHVuZSDDqWNow6lhbmNlIGRvbm7DqWUsIGlsIHkgYSDDqWdhbGVtZW50IGRlcyBmbHVjdHVhdGlvbnMgc2Vsb24gbGUgbW9pcyBjb25zaWTDqXLDqSwgbWFpcyBjZXMgZGlmZsOpcmVuY2VzIHJlc3RlbnQgcmVsYXRpdmVtZW50IHJhaXNvbm5hYmxlcy4gQSB0aXRyZSBkJ2V4ZW1wbGUsIGZhaXNvbnMgbGUgY2FsY3VsIHBvdXIgdW5lIMOpY2jDqWFuY2UgZGUgNCBqb3VyczoKYGBge3J9CmQgJT4lIGZpbHRlcihFY2hlYW5jZSA9PTQpICU+JSBtdXRhdGUoeD0oT2JzLVRfbW95X3JlZl9saXMpL1Rfc3RkX3JlZl9saXMseT0oWGJhci1UX21veV9yZWZfbGlzKS9UX3N0ZF9yZWZfbGlzKSAlPiUgCiAgZ3JvdXBfYnkoTW9udGgpICU+JSBzdW1tYXJpc2UoYmlhaXM9bWVhbih4LXksIG5hLnJtPVQpLCB2YXJpYW5jZT12YXIoeC15LCBuYS5ybT1UKSxzY2U9bWVhbigoeC15KV4yLCBuYS5ybT1UKSkKYGBgCgojIyMgUmVsYXRpb24gZW50cmUgdmFyaWFuY2UgZGUgbCdlbnNlbWJsZSBldCB0ZW1ww6lyYXR1cmUgb2JzZXJ2w6llCgpQYXIgY29udHJlLCBsYSB2YXJpYW5jZSBpbnRlci1tZW1icmUgbmUgc2VtYmxlIGd1w6hyZSByZWxpw6llIMOgIGxhIHRlbXDDqXJhdHVyZSBvYnNlcnbDqWUKYGBge3J9CmQgJT4lIGdncGxvdChhZXMoeD0oT2JzLVRfbW95X3JlZl9saXMpL1Rfc3RkX3JlZl9saXMseT1sb2coVjIvKFRfc3RkX3JlZl9saXNeMikpLGNvbG9yPWFzLmZhY3RvcihNb250aCkpICkrZ2VvbV9wb2ludCgpK2ZhY2V0X3dyYXAofkVjaGVhbmNlKStsYWJzKHg9ImFub21hbGllIGRlIHRlbXDDqXJhdHVyZSIsIHk9IlZhcmlhbmNlIHJlbGF0aXZlIChlbkxvZykiLCBjb2xvcj0iTW9pcyIpCgpgYGAKCkNvbW1lIGxlIG1vbnRyZSBsYSBmaWd1cmUgcHLDqWPDqWRlbnRlLCBsYSB2YWxldXIgbW95ZW5uZSAoZ8Opb23DqXRyaXF1ZSkgZGUgbGEgdmFyaWFuY2UgaW50ZXItbWVtYnJlIGF1Z21lbnRlIGF2ZWMgbCfDqWNow6lhbmNlIGp1c3F1J8OgIGF0dGVpbmRyZSA0MiUgZGUgbGEgdmFyaWFuY2UgY2xpbWF0b2xvZ2lxdWUgw6AgbCfDqWNow6lhbmNlIDkgam91cnMsIGNvbW1lIG9uIHMneSBhdHRlbmQsIG1haXMgcGFzIGxhIGRpc3BlcnNpb24gZGUgc29uIGxvZ2FyaXRobWUsIHF1aSwgYXUgY29udHJhaXJlLCBkaW1pbnVlIC4KCmBgYHtyfQpkICU+JSBtdXRhdGUoeT1sb2coVjIvKFRfc3RkX3JlZl9saXNeMikpKSAlPiUgZ3JvdXBfYnkoRWNoZWFuY2UpICU+JSBzdW1tYXJpc2UobW95Z2VvbXZhcmlhbmNlPWV4cChtZWFuKHksIG5hLnJtPVQpKSwgVmFyTG9nVmFyaWFuY2U9dmFyKHksIG5hLnJtPVQpKQpgYGAKCkzDoCBlbmNvcmUsIGxlIG1vaXMgZGUgbCdhbm7DqWUgbmUgc2VtYmxlIGd1w6hyZSBpbmZsdWVudCB2aXMgw6AgdmlzIGRlIGNlcyBjYXJhY3TDqXJpc3RpcXVlcywgdm9pciBwYXIgZXhlbXBsZSBsZSB0YWJsZWF1IHBvdXIgbCfDqWNow6lhbmNlIMOgIHF1YXRyZSBqb3Vycy4KCmBgYHtyfQpkICU+JSBmaWx0ZXIoRWNoZWFuY2UgPT00KSAlPiUgbXV0YXRlKHk9bG9nKFYyLyhUX3N0ZF9yZWZfbGlzXjIpKSkgJT4lIGdyb3VwX2J5KE1vbnRoKSAlPiUgc3VtbWFyaXNlKG1veWdlb212YXJpYW5jZT1leHAobWVhbih5LCBuYS5ybT1UKSksIFZhckxvZ1ZhcmlhbmNlPXZhcih5LCBuYS5ybT1UKSkKYGBgCgoKIyMjIFJlbGF0aW9uIGVudHJlIHZhcmlhbmNlIGRlIGwnZW5zZW1ibGUgZXQgw6ljYXJ0IGRlIGxhIHRlbXDDqXJhdHVyZSBvYnNlcnbDqWUgw6AgbGEgbW95ZW5uZSBkJ2Vuc2VtYmxlCgpPbiBvYnNlcnZlIGVuZmluIHVuIGxpZW4gZGUgbCfDqWNhcnQgZW50cmUgb2JzZXJ2YXRpb24gZXQgbW95ZW5uZSBkJ2Vuc2VtYmxlIMOgIGxhIHZhcmlhYmlsaXTDqSBpbnRlci1tZW1icmUuIExhIGZpZ3VyZSBjaS1hcHLDqHMgaWxsdXN0cmUgY2V0dGUgbGlhaXNvbiAoZGFucyBsJ8OpY2hlbGxlIGRlcyDDqWNhcnQtdHlwZXMgcmVsYXRpZnMpLiBKZSByZXN0ZSBwb3VyIGxlIG1vbWVudCBzYW5zIGV4cGxpY2F0aW9uIGVuIGNlIHF1aSBjb25jZXJuZSBsYSB2YWxldXIgZGUgbGEgcGVudGUsIHByb2NoZSBkZSAkMSQuIAoKYGBge3J9CmQgJT4lICBtdXRhdGUoeD0oVjIvKFRfc3RkX3JlZl9saXNeMikpLHk9KChPYnMtWGJhcikvVF9zdGRfcmVmX2xpcyleMikgJT4lIApnZ3Bsb3QoYWVzKHg9KHgpXjAuNSx5PSh5KV4wLjUpKStnZW9tX3BvaW50KGFlcyggY29sb3I9YXMuZmFjdG9yKE1vbnRoKSkpK2dlb21fc21vb3RoKHNlPTAsIGNvbG9yPSdibGFjaycpK2dlb21fYWJsaW5lKGludGVyY2VwdD0wLCBzbG9wZT0xLCBjb2xvcj0nd2hpdGUnKStmYWNldF93cmFwKH5FY2hlYW5jZSkrbGFicyh5PSLDqWNhcnQgYWJzb2x1IHByw6l2aXNpb24tdGVtcGVyYXR1cmUgbm9ybWFsaXPDqSIsIHg9InN0ZCByZWxhdGlmIiwgY29sb3I9Ik1vaXMiKQpgYGAKT24gc291aGFpdGUgc2Ugc2VydmlyIGRlIGNlIGxpZW4sIG3Dqm1lIHMnaWwgcydhdsOocmUgYXNzZXogZmxvdSwgcG91ciBjb25zdHJ1aXJlIHVuIG1vZMOobGUgZGUgcHLDqXZpc2lvbiBwcm9iYWJpbGlzdGUgcmVsaWFudCBsZXMgc3RhdGlzdGlxdWVzIHLDqXN1bcOpZXMgJFxiYXJ7WH1fdCwgVl90XjIkIGRlIGwnZW5zZW1ibGUgZXQgbCdvYnNlcnZhdGlvbiBjaWJsZSAkXHRoZXRhX3QkLgoKIyBMZSBjaGFsbGVuZ2UKCiMjIE9iamVjdGlmcwoKTCdvYmplY3RpZiBkZSBjZSB0cmF2YWlsIGVzdDoKCiAgKiBkJ3V0aWxpc2VyIGxhIG3DqXRob2RlIGNvaMOpcmVudGUgZHUgQkZTIHBvdXIgY29uc3RydWlyZSB1bmUgcHLDqXZpc2lvbiBwcm9iYWJpbGlzdGUgw6AgcGFydGlyIGR1IG1vZMOobGUgw6ljaGFuZ2VhYmxlIG5vcm1hbCDDoCBkZXV4IHBpdm90cywKCiAgKiBkZSBtb250cmVyIHNlcyBwZXJmb3JtYW5jZXMgc3VyIGxlcyBkb25uw6llcyBkZSB0ZW1ww6lyYXR1cmVzIGRlIFZvdWdsYW5zLCDDoCBkaXZlcnNlcyDDqWNow6lhbmNlcy4gCiAgCiAgKiBkZSBjb21wYXJlciBzZXMgcGVyZm9ybWFuY2VzIMOgIGNlbGxlcyBkZXMgbcOpdGhvZGVzIGNvdXJhbnRlcyBFTU9TIGV0IEJNQSwgcGFyIGV4ZW1wbGUuCiAgCiAgKiBkZSBtb250cmVyIGNvbW1lbnQgaW50w6lncmVyIGRhbnMgbGUgbcOqbWUgc2Now6ltYSBsZXMgaW5mb3JtYXRpb25zIGlzc3VlcyBkJ2F1dHJlcyBzeXN0w6htZXMgZCdlbnNlbWJsZXMuCiAgCiMjIExlcyBwZXJmb3JtYW5jZXMgw6AgYmF0dHJlCgogIExhIGZpZ3VyZSBjaS1hcHLDqHMgYW5ub25jZSBsJ29iamVjdGlmIMOgIGJhdHRyZSBlbiB0ZXJtZSBkZSBDUlBTIHBhciByYXBwb3J0IMOgIGxhIG3DqXRob2RlIGQnZW5zZW1ibGUgZHUgQ0VQLgogIAoKYGBge3J9CmxhYmVsPC1kYXRhLmZyYW1lKAogIEVjaGVhbmNlPWMoOCw1KSwKICBDUlBTPSBjKDIuMDUsMS4xKSwKICBsYWJlbD1jKCJDbGltYXRvbG9naWUiLCJDRVAiKQopCmQgJT4lIGZpbHRlcihZZWFyPjIwMTEpICU+JSBncm91cF9ieShFY2hlYW5jZSkgJT4lIG11dGF0ZShjcnBzX2Vucz1jcnBzKG9icz1PYnMscHJlZCA9IGRhdGEuZnJhbWUoWGJhcixWMl4wLjUsIG5hLnJtPVQpKSRjcnBzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjcnBzX2NsaW09Y3JwcyhvYnM9T2JzLHByZWQgPSBkYXRhLmZyYW1lKFRfbW95X3JlZl9saXMsVF9zdGRfcmVmX2xpcywgbmEucm09VCkpJGNycHMpICU+JSBzdW1tYXJpemUoQ1JQU19lbnM9bWVhbihjcnBzX2VucyxuYS5ybT1UKSxDUlBTX2NsaW09bWVhbihjcnBzX2NsaW0sbmEucm09VCkpICU+JSAKICBnZ3Bsb3QoYWVzKHg9RWNoZWFuY2UpKStnZW9tX2xpbmUoYWVzKHk9Q1JQU19jbGltKSxjb2xvcj0ncmVkJykrZ2VvbV9saW5lKGFlcyh5PUNSUFNfZW5zKSkrZ2VvbV9wb2ludChhZXMoeT1DUlBTX2NsaW0pLGNvbG9yPSdyZWQnKStnZW9tX3BvaW50KGFlcyh5PUNSUFNfZW5zKSkrbGFicyh5PSJDUlBTIikrZ2VvbV9sYWJlbChkYXRhPWxhYmVsLGFlcyh5PUNSUFMsIGxhYmVsPWxhYmVsKSkKYGBgCgpPbiBjaGVyY2hlcmEgw6lnYWxlbWVudCDDoCB2w6lyaWZpZXIgbGEgY2FsaWJyYXRpb24sIHBhciBleGVtcGxlIGVuIHRlc3RhbnQgbGUgY2FyYWN0w6hyZSB1bmlmb3JtZSBkZSBsZXVyICpwcm9iYWJpbGl0eSBpbnRlZ3JhbCB0cmFuc2Zvcm1zKi4KYGBge3J9CmQgJT4lIGZpbHRlcihZZWFyPjIwMTEpICU+JSAgbXV0YXRlKHBpdF9lbnM9Y3JwcyhvYnM9T2JzLHByZWQgPSBkYXRhLmZyYW1lKFhiYXIsVjJeMC41LCBuYS5ybT1UKSkkcGl0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwaXRfY2xpbT1jcnBzKG9icz1PYnMscHJlZCA9IGRhdGEuZnJhbWUoVF9tb3lfcmVmX2xpcyxUX3N0ZF9yZWZfbGlzLCBuYS5ybT1UKSkkcGl0KSAlPiUKICBnZ3Bsb3QoKStnZW9tX2hpc3RvZ3JhbShtYXBwaW5nPWFlcyh4PXBpdF9jbGltKSxzdGF0PSJkZW5zaXR5IiwgY29sb3I9J3JlZCcpKwogIGdlb21faGlzdG9ncmFtKG1hcHBpbmc9YWVzKHg9cGl0X2Vucyksc3RhdD0iZGVuc2l0eSIscG9zaXRpb24gPSAiZG9kZ2UiLCBjb2xvcj0iYmx1ZSIsIGFscGhhPTAuMykrZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSkrZmFjZXRfd3JhcCh+RWNoZWFuY2UpK2xhYnMoeD0iUHJvYmFiaWxpdHkgSW50ZWdyYWwgVHJhbnNmb3JtIikKYGBgCgojIyBEaXZpc2VyIGwnw6ljaGFudGlsbG9uIGVuIGFwcHJlbnRpc3NhZ2UrdmFsaWRhdGlvbgoKUG91ciB0ZXN0ZXIgbGEgcGVyZm9ybWFuY2UgZGVzIGRpdmVyc2VzIG3DqXRob2Rlcywgb24gcsOpYWxpc2VyYSBsJ2luZsOpcmVuY2Ugc3VyIHVuIMOpY2hhbnRpbGxvbiBkJ2FwcHJlbnRpc3NhZ2UgKHBhciBleGVtcGxlIDIwMDUtMjAwOCkgZXQgb24gdmFsaWRlcmEgc3VyIGwnw6ljaGFudGlsbG9uIHJlc3RhbnQgKHBhciBleGVtcGxlIDIwMTEtMjAxNSkKCmBgYHtyfQpkICU+JSBmaWx0ZXIoWWVhciA8IDIwMTAsICFpcy5uYShDYWxlbmRhaXJlKSkgJT4lIAogIG11dGF0ZSh0aGV0YT0oT2JzLVRfbW95X3JlZl9saXMpL1Rfc3RkX3JlZl9saXMsCiAgICAgICAgIHhiYXI9KFhiYXItVF9tb3lfcmVmX2xpcykvVF9zdGRfcmVmX2xpcywKICAgICAgICAgdjI9ICBWMi8oVF9zdGRfcmVmX2xpc14yKSApICU+JSAKICAgZmlsdGVyKCFpcy5uYSh0aGV0YSksCiAgICAgICAgICAhaXMubmEoeGJhciksCiAgICAgICAgICAhaXMubmEodjIpKSAtPiAKICBsZWFybmluZ19zYW1wbGUKZCAlPiUgZmlsdGVyKFllYXIgPiAyMDEwICwhaXMubmEoQ2FsZW5kYWlyZSkpICU+JSAKICBtdXRhdGUodGhldGE9KE9icy1UX21veV9yZWZfbGlzKS9UX3N0ZF9yZWZfbGlzLAogICAgICAgICB4YmFyPShYYmFyLVRfbW95X3JlZl9saXMpL1Rfc3RkX3JlZl9saXMsCiAgICAgICAgIHYyPSAgVjIvKFRfc3RkX3JlZl9saXNeMikgKSAlPiUgCiBmaWx0ZXIoIWlzLm5hKHRoZXRhKSwKICAgICAgICFpcy5uYSh4YmFyKSwKICAgICAgICFpcy5uYSh2MikpIC0+IAogIHZhbGlkYXRpb25fc2FtcGxlCmBgYAoKCiMgTW9kw6hsZXMgZCdlbnNlbWJsZQoKIyMgTW9kw6hsZSBHYW1tYS1Ob3JtYWwgw6AgZGV1eCBwaXZvdHMKClJhcHBlbG9ucyBsYSBzdWdnZXN0aW9uIGRlIEphY3F1ZXMgZGFucyBzb24gZG9jdW1lbnQgKkVuc2VtYmxlU2Fpc29uLioKTGVzIG1lbWJyZXMgcydhcHB1aWVudCBzdXIgZGV1eCBwaXZvdHMgbGF0ZW50cyAkWl97MXR9JCBldCAkWl97MnR9JCwgZGUgdGVsbGUgc29ydGUgcXVlIGxlIG1vZMOobGUgw6ljaGFuZ2VhYmxlIHMnw6ljcml0OgoKJCR7WF97dHN9fSA9IFxhbHBoYSAgKyBcYmV0YSB7Wl97MXR9fSArIFxsYW1iZGEgXHNpZ21hIHtaX3sydH19XnsgLSBPLjV9e1x2YXJlcHNpbG9uIF97dHN9fSQkCgpvw7kgJFx2YXJlcHNpbG9uIF97dHN9XHNpbSBOKDAsMSkkIHRhbmRpcyBxdWUgJFxzaWdtYSQgcGV1dCDDqnRyZSBwcmlzIMOpZ2FsIMOgICQxJC4KTGVzIHBpdm90cyBsYXRlbnRzIHNvbnQgbXVuaXMgZGUgcHJpb3JzCiQkXGJlZ2lue2dhdGhlcmVkfQogIHtaX3syLHR9fSBcc2ltIEdhbW1hKGcsMSkgXGhmaWxsIFxcCiAge1pfezF0fX18e1pfezJ0fX0gXHNpbSBOKDAse1xzaWdtYSBeMn17Wl97MnR9fV57IC0gMX0pIFxoZmlsbCBcXCAKXGVuZHtnYXRoZXJlZH0kJAoKClRyYXZhaWxsYW50IGF2ZWMgbGVzIHN0YXRpc3RpcXVlcyBleGhhdXNpdmVzLCBvbiDDqWNyaXJhCiQkXGJlZ2lue2dhdGhlcmVkfQogIHt7XGJhciBYfV90fSBcc2ltIE4oXGFscGhhICArIFxiZXRhIHtaX3sxdH19LFxmcmFje3t7XGxhbWJkYSBeMn17XHNpZ21hIF4yfX19e3tTe1pfezJ0fX19fSkgXGhmaWxsIFxcCiAgXHN1bVxsaW1pdHNfe3MgPSAxfV5TIHt7eyh7WF97dHN9fSAtIHt7XGJhciBYfV90fSl9XjJ9fSAgXHNpbSBcZnJhY3t7e1xsYW1iZGEgXjJ9e1xzaWdtYSBeMn19fXt7e1pfezJ0fX19fXtcY2hpIF4yfShTIC0gMSkgXGhmaWxsIFxcIApcZW5ke2dhdGhlcmVkfSQkCgoKClNvaXQgZW5jb3JlLCBlbiBwb3NhbnQgJFZfe3R9XnsyfT1cZnJhY3t7XHN1bVxsaW1pdHNfe3MgPSAxfV5TIHt7eyh7WF97dHN9fSAtIHt7XGJhciBYfV90fSl9XjJ9fSB9fXt7UyAtIDF9fSQsIGxlIG1vZMOobGUgZCfDqWNoYW50aWxsb25uYWdlIMOgICR0JCwgcGFyIG9ydGhvZ29uYWxpdMOpIGRlcyBkZXV4IHN0YXRpc3RpcXVlcyBleGhhdXN0aXZlcyBtb3llbm5lIGV0IHZhcmlhbmNlcyBlbXBpcmlxdWVzLCBzZSByw6lzdW1lIHBhciBsZXMgZGV1eCDDqXF1YXRpb25zOgokJFxiZWdpbntnYXRoZXJlZH0KICB7e1xiYXIgWH1fdH0gXHNpbSBOKFxhbHBoYSAgKyBcYmV0YSB7Wl97MXR9fSxcZnJhY3t7e1xsYW1iZGEgXjJ9e1xzaWdtYSBeMn19fXt7U3taX3sydH19fX0pIFxoZmlsbCBcXAogIHtWX3t0fV57Mn19IFxzaW0gR2FtbWFcbGVmdCgge1xmcmFje3tTIC0gMX19ezJ9LFxmcmFje3t7Wl97MnR9fShTIC0gMSl9fXt7MntcbGFtYmRhIF4yfXtcc2lnbWEgXjJ9fX19IFxyaWdodCkgXGhmaWxsIFxcIApcZW5ke2dhdGhlcmVkfSQkCgpMJ2luZsOpcmVuY2UgZGVzIGNvZWZmaWNpZW50cyAkXGFscGhhICxcYmV0YSAsXGxhbWJkYSAsIGckIHNlcmEgbWVuw6llIHN1ciBsJyDDqWNoYW50aWxsb24gZCdhcHByZW50aXNzYWdlLgpQcmVub25zIHBhciBleGVtcGxlIGwnw6ljaGVhbmNlIMOgIDQgam91cnMgZGVzIGFubsOpZXMgMjAwNS0yMDA4LgoKYGBge3J9CmxlYXJuaW5nX3NhbXBsZSAgJT4lIGZpbHRlciggRWNoZWFuY2UgPT0gNCkgJT4lCiAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHRoZXRhLHhiYXIsdjIpIC0+IGFwcHJlbnRpc3NhZ2UKYGBgCgpPbiBwcm9jw6hkZSDDoCBsJ2VzdGltYXRpb24gYmF5w6lzaWVubmUgZGUgJFxhbHBoYSAsXGJldGEgLFxsYW1iZGEgLCBnJCBncsOiY2UgYXUgbG9naWNpZWwgZCdpbmbDqXJlbmNlIGJhecOpc2llbm5lICpKYWdzKi4gKEEgbm90ZXIgcXUnb24gcG91cnJhaXQgw6lnYWxlbWVudCB1dGlsaXNlciBTVEFOLikKCmBgYHtyfQptb2RlbF9zdHJpbmcgPC0gIgptb2RlbHsKYTwtKFMtMSkvMgpmb3IgKHQgaW4gMTpOKXsKbVt0XSA8LSBhbHBoYStiZXRhKloxW3RdCnByZWNpW3RdPC0gWjJbdF0vKGxhbWJkYTIpCnhiYXJbdF0gfiBkbm9ybShtW3RdLCBwcmVjaVt0XSkKYlt0XTwtcHJlY2lbdF0qYQp2Mlt0XSB+IGRnYW1tYShhLGJbdF0pCiMgbGF0ZW50ZXMKWjJbdF0gfiBkZ2FtbWEoZywxKQpaMVt0XSB+IGRub3JtKDAsIDEpCn0KYWxwaGEgfiBkdW5pZigtMTAsMTApCmJldGEgfiBkdW5pZigwLDEwKQpsYW1iZGEyIH4gZHVuaWYoMCwxMCkKZyB+IGR1bmlmKDAuNSwxMCkKfQoiCnBhcmFtcz1jKCJhbHBoYSIsImJldGEiLCJsYW1iZGEyIiwiZyIpCmRhdGEgPC0gbGlzdCh4YmFyPWFwcHJlbnRpc3NhZ2UkeGJhciwKICAgICAgICAgICAgIHYyPWFwcHJlbnRpc3NhZ2UkdjIsCiAgICAgICAgICAgICBOPWxlbmd0aChhcHByZW50aXNzYWdlJHhiYXIpLAogICAgICAgICAgICAgUz01MCkKdGVtcDwtamFncyhkYXRhID0gZGF0YSwgbW9kZWwuZmlsZSA9IHRleHRDb25uZWN0aW9uKG1vZGVsX3N0cmluZyksCiAgICAgICAgICAgcGFyYW1ldGVycy50by5zYXZlID0gcGFyYW1zLG4uY2hhaW5zID0gMywKICAgICAgICAgICBuLmJ1cm5pbiA9IDUwMDAsbi5pdGVyID0gMTAwMDApCmBgYAoKCmBgYHtyfQp0ZW1wJEJVR1NvdXRwdXQkc2ltcy5tYXRyaXggJT4lIAogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgZHBseXI6OnNlbGVjdChhbHBoYSxiZXRhLGxhbWJkYTIsZykgLT5hbHBoYWJldGFsYW1iZGEyZwogIGFscGhhYmV0YWxhbWJkYTJnICU+JSAKICBnYXRoZXIocGFyYW0sIHZhbHVlKSAlPiUgCiAgZ2dwbG90KGFlc19zdHJpbmcoeD0idmFsdWUiKSkgKwogIGdlb21fZGVuc2l0eShhbHBoYT0wLjUpICsKICBmYWNldF9ncmlkKHBhcmFtfi4sc2NhbGVzID0gImZyZWUiKSAKCmBgYAoKCkwnaW50ZXJjZXB0ICRcYWxwaGEkIGVzdCBwb3NzaWJsZW1lbnQgbnVsLCBsYSBwZW50ZSAkXGJldGEkIHJlc3RhbnQgaW5mw6lyaWV1cmUgw6AgJDEkLgpMYSBjb25uYWlzc2FuY2UgYSBwb3N0ZXJpb3JpIGRlICRnJCBlc3QgYXNzZXogaW5jZXJ0YWluZTsgJFxsYW1iZGFeMiQgZXQgJGckIHNvbnQgZm9ydGVtZW50IGNvcnLDqWzDqXMuCgpgYGB7cn0KZ2dwYWlycyhkYXRhID0gYWxwaGFiZXRhbGFtYmRhMmcpCgpgYGAKCmBgYHtyfQprbml0cjo6a2FibGUocmJpbmQobWVhbj1hcHBseShYID0gYWxwaGFiZXRhbGFtYmRhMmcsMixtZWFuKSxzdGQ9YXBwbHkoWCA9IGFscGhhYmV0YWxhbWJkYTJnLDIsdmFyKV4wLjUsYXBwbHkoWCA9IGFscGhhYmV0YWxhbWJkYTJnLDIsIHF1YW50aWxlLCBwcm9icz1jKDAuMDUsMC4yNSwwLjUsIDAuNzUsIDAuOTUpKSksIGRpZz0zKQprbml0cjo6a2FibGUoY29yKGFscGhhYmV0YWxhbWJkYTJnKSxkaWc9MykKYGBgCgojIyBBbmFseXNlIG1hcmdpbmFsZSBkdSBtb2TDqGxlIMOgIGRldXggcGl2b3RzCgpMYSB2cmFpc2VtYmxhbmNlIMOgICR0JCBzJ8OpY3JpdDoKJCRcYmVnaW57Z2F0aGVyZWR9CiAge0xfdH0gPSBbe3tcYmFyIFh9X3R9LHtWX3R9XjJ8e1pfezF0fX0se1pfMn1dIFxoZmlsbCBcXAogIHtMX3R9IFxwcm9wdG8ge1pfMn1ee1xmcmFjezF9ezJ9fVxleHAgIC0gXGZyYWN7e3taX3sydH19U319e3sye1xsYW1iZGEgXjJ9fX1cbGVmdFx7IHt7e1xsZWZ0KCB7e3tcYmFyIFh9X3R9IC0gXGFscGhhICAtIFxiZXRhIHtaX3sxdH19fSBccmlnaHQpfV4yfX0gXHJpZ2h0XH0gXHRpbWVzIHtaX3sydH19XntcZnJhY3tTfXsyfSAtIFxmcmFjezF9ezJ9fVxleHAgIC0gXGZyYWN7e3taX3sydH19fX17ezJ7XGxhbWJkYSBeMn19fVxsZWZ0XHsge1xsZWZ0KCB7UyAtIDF9IFxyaWdodCl7Vl90fV4yfSBccmlnaHRcfSBcaGZpbGwgXFwgClxlbmR7Z2F0aGVyZWR9JCQKCk9uIHZhIHRyYXZhaWxsZXIgY29tbWUgS3J6eXN6dG9mb3dpY3ogZW4gbsOpZ2xpZ2VhbnQgbGVzIGluY2VydGl0dWRlcyBkZXMgaHlwZXItcGFyYW3DqHRyZXMgZHUgbW9kw6hsZSBtYXJnaW5hbC4gCgpgYGB7cn0KYWxwaGE8LW1lYW4oYWxwaGFiZXRhbGFtYmRhMmdbLCJhbHBoYSJdKQpiZXRhPC1tZWFuKGFscGhhYmV0YWxhbWJkYTJnWywiYmV0YSJdKQpsYW1iZGEyPC1tZWFuKGFscGhhYmV0YWxhbWJkYTJnWywibGFtYmRhMiJdKQpnPC1tZWFuKGFscGhhYmV0YWxhbWJkYTJnWywiZyJdKQpTPC01MApSZXBldCA8LTEwMDAKZGF0YS5mcmFtZShaMj1yZ2FtbWEoUmVwZXQsZywxKSwgWjE9cm5vcm0oUmVwZXQpKSAlPiUgCiAgbXV0YXRlKHhiYXI9YWxwaGErYmV0YSpaMStzcXJ0KGxhbWJkYTIvWjIvUykqcm5vcm0oUmVwZXQpLAogIHYyPWxhbWJkYTIvWjIvKFMtMSkqcmNoaXNxKFJlcGV0LGRmPVMtMSkpICU+JSBmaWx0ZXIodjI8MikgJT4lIAogIGRwbHlyOjpzZWxlY3QoeGJhcix2MikgJT4lIGdhdGhlcihrZXksdmFsdWUpICU+JSBnZ3Bsb3QoYWVzKHg9dmFsdWUsY29sb3I9a2V5KSkrCiAgZ2VvbV9oaXN0b2dyYW0oZGF0YT0gYXBwcmVudGlzc2FnZSAlPiUgZHBseXI6OnNlbGVjdCh4YmFyLHYyKSAlPiUgZmlsdGVyKHYyPDIpICU+JSAgZ2F0aGVyKGtleSx2YWx1ZSksIHN0YXQgPSAiZGVuc2l0eSIpKwogIGdlb21fZnJlcXBvbHkoc3RhdCA9ICJkZW5zaXR5IiwgY29sb3I9J2JsYWNrJykrZmFjZXRfd3JhcCh+a2V5LHNjYWxlcz0iZnJlZSIpCmBgYApTdXIgY2V0dGUgY29tcGFyYWlzb24gZXN0aW1hdGlvbiBlbXBpcmlxdWUgZXQgbW9kw6hsZSwgb24gdm9pdCBxdWUgbGVzIGxvaXMgbWFyZ2luYWxlcyBzZSBjYWxlbnQgcmFpc29ubmFibGVtZW50IGF2ZWMgbm90cmUgbW9kw6hsZSBnYW1tYS1ub3JtYWwgZWNoYW5nZWFibGUsIG1hbGdyw6kgcXVlIG5vdSBzbidheW9ucyBwYXMgdGVudSBjb21wdGUgZGVzIGluY2VydGl0dWRlcyDDoCBwcm9wb3MgZGVzIGh5cGVyLXBhcmFtw6h0cmVzICgkXGFscGhhLFxiZXRhLCBcZ2FtbWFeMiwgZyQpIGR1IG1vZMOobGUgbWFyZ2luYWwuCgojIyBBbmFseXNlIHByw6lxdWVudGllbGxlIGR1IG1vZMOobGUgw6AgZGV1eCBwaXZvdHMKCiMjIyBFeHByZXNzaW9uIGRlIGxhIGNvbmRpdGlvbm5lbGxlIGRlcyBkZXV4IHZhcmlhYmxlcyBwaXZvdHMgbGF0ZW50ZXMKCkxhIHZyYWlzZW1ibGFuY2UgY29tcGzDqHRlIHF1YW50IMOgIGVsbGUgZXN0IGzDqWfDqHJlbWVudCBwbHVzIGNvbXBsaXF1w6llOgokJFxiZWdpbntnYXRoZXJlZH0KICBDe0xfdH0gPSBbe3tcYmFyIFh9X3R9LHtWX3R9XjIse1pfezF0fX0se1pfMn1dID0gW3t7XGJhciBYfV90fSx7Vl90fV4yfHtaX3sxdH19LHtaX3sydH19XSBcdGltZXMgW3taX3sxdH19LHtaX3sydH19XSBcaGZpbGwgXFwKICBDe0xfdH0gXHByb3B0byB7TF90fSBcdGltZXMgXGxlZnQoIHt7Wl8yfV57XGZyYWN7MX17Mn19XGV4cCAgLSBcZnJhY3t7e1pfezJ0fX19fXsyfVpfezF0fV4yfSBccmlnaHQpIFx0aW1lcyBcbGVmdCgge1pfezJ0fV57ZyAtIDF9XGV4cCAgLSB7Wl97MnR9fX0gXHJpZ2h0KSBcaGZpbGwgXFwgClxlbmR7Z2F0aGVyZWR9JCQKCkVuIGTDqXZlbG9wcGFudDoKJCRcYmVnaW57Z2F0aGVyZWR9CiAgQ3tMX3R9IFxwcm9wdG8gXGxlZnQoIHt7Wl8yfV57XGZyYWN7MX17Mn19XGV4cCAgLSBcZnJhY3t7e1pfezJ0fX19fXsyfVxsZWZ0XHsge1pfezF0fV4yICsgXGZyYWN7e1N7XGJldGEgXjJ9fX17e3tcbGFtYmRhIF4yfX19e3tcbGVmdCgge3taX3sxdH19IC0gXGZyYWN7e3t7XGJhciBYfV90fSAtIFxhbHBoYSB9fXtcYmV0YSB9fSBccmlnaHQpfV4yfX0gXHJpZ2h0XH19IFxyaWdodCkgXGhmaWxsIFxcCiAgIFx0aW1lcyBcbGVmdCgge1pfezJ0fV57ZyArIFxmcmFje1N9ezJ9IC0gMX1cZXhwICAtIHtaX3sydH19KDEgKyBcZnJhY3t7XGxlZnQoIHtTIC0gMX0gXHJpZ2h0KXtWX3R9XjJ9fXt7MntcbGFtYmRhIF4yfX19KX0gXHJpZ2h0KSBcaGZpbGwgXFwgClxlbmR7Z2F0aGVyZWR9JCQKCnB1aXMgZW4gcmVncm91cGFudDoKCiQkXGJlZ2lue2dhdGhlcmVkfQogIFpfezF0fV4yICsgXGZyYWN7e1N7XGJldGEgXjJ9fX17e3tcbGFtYmRhIF4yfX19e1xsZWZ0KCB7e1pfezF0fX0gLSBcZnJhY3t7e3tcYmFyIFh9X3R9IC0gXGFscGhhIH19e1xiZXRhIH19IFxyaWdodCleMn0gPSBcbGVmdCgge1xmcmFje3tTe1xiZXRhIF4yfSArIHtcbGFtYmRhIF4yfX19e3t7XGxhbWJkYSBeMn19fX0gXHJpZ2h0KXtcbGVmdCgge1pfezF0fV57fSAtIFxsZWZ0KCB7XGZyYWN7e3t7XGJhciBYfV90fSAtIFxhbHBoYSB9fXtcYmV0YSB9fSBccmlnaHQpXGZyYWN7e1N7XGJldGEgXjJ9fX17e1N7XGJldGEgXjJ9ICsge1xsYW1iZGEgXjJ9fX19IFxyaWdodCleMn0gXGhmaWxsIFxcCiAgICsgXGxlZnRceyB7XGxlZnQoIHtcZnJhY3t7U3t7XGxlZnQoIHt7e1xiYXIgWH1fdH0gLSBcYWxwaGEgfSBccmlnaHQpfV4yfX19e3tTe1xiZXRhIF4yfSArIHtcbGFtYmRhIF4yfX19fSBccmlnaHQpfSBccmlnaHRcfSBcaGZpbGwgXFwgClxlbmR7Z2F0aGVyZWR9JCQKCmxhIGNvbmp1Z2Fpc29uIGFkaG9jIHBlcm1ldCBkJ29idGVuaXIgbGVzIGNvbmRpdGlvbm5lbGxlcyBhIHBvc3RlcmlvcmkKCiQkXGJlZ2lue2dhdGhlcmVkfQogIHtaX3sydH19fHt7XGJhciBYfV90fSx7Vl90fV4yIFxzaW0gR2FtbWEoZyArIFxmcmFje1N9ezJ9LDEgKyBcZnJhY3t7XGxlZnQoIHtTIC0gMX0gXHJpZ2h0KXtWX3R9XjJ9fXt7MntcbGFtYmRhIF4yfX19ICsgXGxlZnQoIHtcZnJhY3t7U3t7XGxlZnQoIHt7e1xiYXIgWH1fdH0gLSBcYWxwaGEgfSBccmlnaHQpfV4yfX19e3syXGxlZnQoIHtTe1xiZXRhIF4yfSArIHtcbGFtYmRhIF4yfX0gXHJpZ2h0KX19fSBccmlnaHQpKSBcaGZpbGwgXFwKICB7Wl97MXR9fXx7Wl97MnR9fSx7e1xiYXIgWH1fdH0se1ZfdH1eMiBcc2ltIE4oXGxlZnQoIHtcZnJhY3t7e3tcYmFyIFh9X3R9IC0gXGFscGhhIH19e1xiZXRhIH19IFxyaWdodClcZnJhY3t7U3tcYmV0YSBeMn19fXt7U3tcYmV0YSBeMn0gKyB7XGxhbWJkYSBeMn19fSx7XGxlZnRceyB7XGxlZnQoIHtcZnJhY3t7U3tcYmV0YSBeMn0gKyB7XGxhbWJkYSBeMn19fXt7e1xsYW1iZGEgXjJ9fX19IFxyaWdodCl7Wl97MnR9fX0gXHJpZ2h0XH1eeyAtIDF9fSkgXGhmaWxsIFxcIApcZW5ke2dhdGhlcmVkfSQkCgoKIyMjIyBOb3RhOgpKZSBwZW5zZSBxdScgaWwgeSBhIHVuZSBlcnJldXIgdHlwb2dyYXBoaXF1ZSBkYW5zIGxlIHBhcGllciBkZSBKYWNxdWVzIGVuIHBhZ2UgOCBwb3VyIGxlIGNhbGN1bCBkZSBsJ2VzcMOpcmFuY2UgZGUgJFpfMSQuCgojIyMgQXBwcmVudGlzc2FnZSBkJ3VuIGxpZW4gZGVzIGRldXggdmFyaWFibGVzIHBpdm90cyBsYXRlbnRlcyBhdmVjIGxhIHZhcmlhYmxlIGNpYmxlCgpQb3VyIGNoYXF1ZSBzaXR1YXRpb24gZGUgbGEgYmFzZSAgZCdhcHByZW50aXNzYWdlLCBub3VzIGfDqW7DqXJvbnMgdW4gJFpfMSh0KSQgcXVlIG5vdXMgbWV0dG9ucyBlbiByZWdhcmQgYXZlYyBsYSBjaWJsZS4KYGBge3J9Ck48LWRpbShhcHByZW50aXNzYWdlKVsxXQphcHByZW50aXNzYWdlICU+JSBtdXRhdGUoYV9aMj1nK1MvMiwgICAgICAgICAgICAgICAgICAgICAgIGJfWjI9MSsoUy0xKSp2Mi8yL2xhbWJkYTIrUyooKHhiYXItYWxwaGEpXjIpLzIvKFMqYmV0YV4yK2xhbWJkYTIpLAptX1oxPSAgICh4YmFyLWFscGhhKSpTKmJldGEgLyhTKmJldGFeMitsYW1iZGEyKSAsCnByZWNpc2lvbl9aMT0oUypiZXRhXjIrbGFtYmRhMikvbGFtYmRhMiwgCloyPXJnYW1tYShOLGFfWjIsYl9aMiksCloxPXJub3JtKE4sbV9aMSwocHJlY2lzaW9uX1oxKloyKV4tMC41KSkgJT4lIGdncGxvdChhZXMoeD10aGV0YSwgeT1aMSkpKwogIGdlb21fcG9pbnQoKStnZW9tX3Ntb290aChtZXRob2Q9J2xtJykrZ2VvbV9hYmxpbmUoc2xvcGU9MSxpbnRlcmNlcHQ9MCwgY29sb3I9IndoaXRlIikKYGBgCgpMYSBzdWdnZXN0aW9uIGRlIEphY3F1ZXMgZXN0IGRlIHJlcGFzc2VyIGVuIG1hcmdpbmFsZSBldCBkZSB0cmF2YWlsbGVyIGF2ZWMgbGVzIFFOVDoKCiogbGEgY2libGUgcXVpIGEgw6l0w6kgY2VudHLDqWUgcsOpZHVpdGUgZXN0IGTDqWrDoCBOKDAsMSkuCgoqICRaXzEkIGVzdCBtYXJnaW5hbGVtZW50IFN0dWRlbnQgw6AgJDJnJCBkZWdyw6lzIGRlIGxpYmVydMOpLiBRdWFudCDDoCAkZ157MC41fSBcdGltZXMgWl8xJCwgaWwgZXN0IFN0dWRlbnQgdW5pdGFpcmUgw6AgJDJnJCBkZWdyw6lzIGRlIGxpYmVydMOpLgoKYGBge3J9CmFwcHJlbnRpc3NhZ2UgJT4lIG11dGF0ZShhX1oyPWcrUy8yLCAgICAgICAgICAgICAgICAgICAgICAgYl9aMj0xKyhTLTEpKnYyLzIvbGFtYmRhMitTKigoeGJhci1hbHBoYSleMikvMi8oUypiZXRhXjIrbGFtYmRhMiksCm1fWjE9ICAgKHhiYXItYWxwaGEpKlMqYmV0YSAvKFMqYmV0YV4yK2xhbWJkYTIpICwKcHJlY2lzaW9uX1oxPShTKmJldGFeMitsYW1iZGEyKS9sYW1iZGEyLCAKWjI9cmdhbW1hKE4sYV9aMixiX1oyKSwKWjE9cm5vcm0oTixtX1oxLChwcmVjaXNpb25fWjEqWjIpXi0wLjUpLApRTlRfWjE9IHFub3JtKHB0KHNxcnQoZykqWjEsZGY9MipnKSkpICU+JSBnZ3Bsb3QoYWVzKHg9dGhldGEsIHk9UU5UX1oxKSkrCiAgZ2VvbV9wb2ludCgpK2dlb21fc21vb3RoKG1ldGhvZD0nbG0nKStnZW9tX2FibGluZShzbG9wZT0xLGludGVyY2VwdD0wLCBjb2xvcj0id2hpdGUiKQpgYGAKCmBgYHtyfQphcHByZW50aXNzYWdlICU+JSBtdXRhdGUoYV9aMj1nK1MvMiwgICAgICAgICAgICAgICAgICAgICAgIGJfWjI9MSsoUy0xKSp2Mi8yL2xhbWJkYTIrUyooKHhiYXItYWxwaGEpXjIpLzIvKFMqYmV0YV4yK2xhbWJkYTIpLAptX1oxPSAgICh4YmFyLWFscGhhKSpTKmJldGEgLyhTKmJldGFeMitsYW1iZGEyKSAsCnByZWNpc2lvbl9aMT0oUypiZXRhXjIrbGFtYmRhMikvbGFtYmRhMiwgCloyPXJnYW1tYShOLGFfWjIsYl9aMiksCloxPXJub3JtKE4sbV9aMSwocHJlY2lzaW9uX1oxKloyKV4tMC41KSwKUU5UX1oxPSBxbm9ybShwdChzcXJ0KGcpKloxLGRmPTIqZykpKSAtPiBhcHByZW50aXNzYWdlCnN1bW1hcnkoIGxtKFFOVF9aMX50aGV0YS0xLCBkYXRhPWFwcHJlbnRpc3NhZ2UpKQpybz1sbShRTlRfWjF+dGhldGEtMSwgZGF0YT1hcHByZW50aXNzYWdlKSRjb2VmCmBgYAoKU3VyIGwnw6ljaGFudGlsbG9uIGQnYXBwcmVudGlzc2FnZSwgb24gdHJvdXZlIHVuIGNvZWZmaWNpZW50IGRlIGNvcnLDqWxhdGlvbiAkXGhhdHtccmhvfT0wLjg4MDMkIGVudHJlIGxlIFFOVCBkZSBsYSBjaWJsZSBldCBsZSBRTlQgZCd1bmUgdmFsZXVyIHRpcsOpZSBhdSBoYXNhcmQgZGFucyBsYSBsb2kgY29uZGl0aW9ubmVsbGUgZHUgcHJlbWllciBwaXZvdCBzYWNoYW50IGxlcyBkZXV4IHN0YXRpc3RpcXVlcyByw6lzdW1hbnQgbCdlbnNlbWJsZSDDqWNoYW5nZWFibGUuCgojIyBBbmFseXNlIHByw6lkaWN0aXZlIGdyw6JjZSBhdSBtb2TDqGxlIMOgIGRldXggcGl2b3RzCgpDb25zaWTDqXJvbnMgbWFpbnRlbmFudCBsJ8OpY2hhbnRpbGxvbiBkZSB2YWxpZGF0aW9uIG91IGwnb24gdmEgYXBwbGlxdWVyIGxlIGNhbGN1bCBwcsOpZGljdGlmOgoKJFtcdGhldGF8WF09XGludF97UU5UfSBbXHRoZXRhfFFOVF0gXHRpbWVzIFtRTlR8WF0gXHRpbWVzIGRRTlQkCgpFdCBzaSBsJ29uIGRpc3Bvc2UgZCd1biBwcmlvciBpbmZvcm1hdGlmICRbXHRoZXRhXSQgcG91ciBsYSBjaWJsZToKCiQkW1x0aGV0YXxYXT1caW50X3tRTlR9IFxmcmFje1tRTlR8XHRoZXRhXVx0aW1lcyBbXHRoZXRhXX17W1FOVF19IFx0aW1lcyBbUU5UfFhdIFx0aW1lcyBkUU5UJCQKU2kgb24gdXRpbGlzZSBsYSBjb25qdWdhaXNvbiBub3JtYWxlIGF2ZWMgdW4gcHJpb3IgJE4obV90LHNfdF4yKSQgcG91ciAkW1x0aGV0YV90XSQsCiRbXHRoZXRhfFFOVF0kIGVzdCAkTihtJ190LHMnX3ReMikkIGF2ZWMKCiogJFxmcmFjezF9e3MnXjJ9PVxmcmFjezF9e3NeMn0rXGZyYWN7XHJob14yfXsxLVxyaG9eMn0kIAoKKiAkXGZyYWN7bSd9e3MnXjJ9PVxmcmFje219e3NeMn0rXGZyYWN7UU5UfXtccmhvfVxmcmFje1xyaG9eMn17MS1ccmhvXjJ9JAoKcG91ciAkbT0wLCBzPTEkLCBjJ2VzdCDDoCBkaXJlIHBvdXIgdW4gcHJpb3IgY2xpbWF0b2xvZ2lxdWUgcG91ciBwcm9kdWlyZSB1bmUgcHLDqXZpc2lvbiBwcm9iYWJpbGlzdGUgY2FsaWJyw6llLCBvbiByZXRyb3V2ZSBsZXMgcsOpc3VsdGF0cyBkZSBsYSByw6lncmVzc2lvbiAkW1x0aGV0YXxRTlRdJCBlc3QgJE4oXHJobyBcdGltZXMgUU5ULDEtXHJob14yKSQKCmBgYHtyfQp2YWxpZGF0aW9uX3NhbXBsZSAlPiUgZmlsdGVyKEVjaGVhbmNlPT00KSAlPiUgbXV0YXRlKGFfWjI9ZytTLzIsICAgICAgICAgICAgICAgICAgICAgICBiX1oyPTErKFMtMSkqdjIvMi9sYW1iZGEyK1MqKCh4YmFyLWFscGhhKV4yKS8yLyhTKmJldGFeMitsYW1iZGEyKSwKbV9aMT0gICAoeGJhci1hbHBoYSkqUypiZXRhIC8oUypiZXRhXjIrbGFtYmRhMikgLApwcmVjaXNpb25fWjE9KFMqYmV0YV4yK2xhbWJkYTIpL2xhbWJkYTIpIC0+dmFsaWRhdGlvbiAKYGBgCmBgYHtyfQpHZW5lcmVNZWFuUHJlZD1mdW5jdGlvbihhX1oyLGJfWjIsbV9aMSxwcmVjaXNpb25fWjEpewogIFJlcD0xMDAKICB6Mj1yZ2FtbWEoUmVwLGFfWjIsYl9aMikKICB6MT1ybm9ybShSZXAsbV9aMSwocHJlY2lzaW9uX1oxKnoyKV4tMC41KQogIFFOVD0gcW5vcm0ocHQoc3FydChnKSp6MSxkZj0yKmcpKQogIG1wcmVkPXJvKm1lYW4oUU5UKQp9CkdlbmVyZVZhclByZWQ9ZnVuY3Rpb24oYV9aMixiX1oyLG1fWjEscHJlY2lzaW9uX1oxKXsKICBSZXA9MTAwCiAgejI9cmdhbW1hKFJlcCxhX1oyLGJfWjIpCiAgejE9cm5vcm0oUmVwLG1fWjEsKHByZWNpc2lvbl9aMSp6MileLTAuNSkKICBRTlQ9IHFub3JtKHB0KHNxcnQoZykqejEsZGY9MipnKSkKICB2cHJlZD1ybypybyp2YXIoUU5UKSsoMS1yb14yKQp9CnZhbGlkYXRpb24gJT4lIGdyb3VwX2J5KERhdGUpICU+JSAKICBtdXRhdGUobXByZWQ9R2VuZXJlTWVhblByZWQoYV9aMixiX1oyLG1fWjEscHJlY2lzaW9uX1oxKSwKICAgICAgICAgdnByZWQ9R2VuZXJlVmFyUHJlZChhX1oyLGJfWjIsbV9aMSxwcmVjaXNpb25fWjEpKSAlPiUKICB1bmdyb3VwKCkgJT4lIG11dGF0ZShjcnBzX2NsaW09Y3JwcyhvYnM9dGhldGEscHJlZCA9IGRhdGEuZnJhbWUoMCp4YmFyLDEsIG5hLnJtPVQpKSRjcnBzLAogICAgICAgICAgICAgIGNycHNfZW5zPWNycHMob2JzPXRoZXRhLHByZWQgPSBkYXRhLmZyYW1lKHhiYXIsdjJeMC41LCBuYS5ybT1UKSkkY3JwcywKICAgICAgICAgICAgICBjcnBzX2tyej1jcnBzKG9icz10aGV0YSxwcmVkID0gZGF0YS5mcmFtZShtcHJlZCx2cHJlZF4wLjUsIG5hLnJtPVQpKSRjcnBzCiAgICAgICAgICAgICAgKSAlPiUgZ3JvdXBfYnkoWWVhcikgJT4lIAogIHN1bW1hcml6ZShDUlBTX2Vucz1tZWFuKGNycHNfZW5zLG5hLnJtPVQpLENSUFNfY2xpbT1tZWFuKGNycHNfY2xpbSxuYS5ybT1UKSwgCiAgICAgICAgICAgIENSUFNfa3J6PW1lYW4oY3Jwc19rcnosbmEucm09VCksIGNvdW50cz1uKCkpCmBgYAoKCgojIyBFTU9TCgpKZSB2ZXV4IGNvbXBhcmVyIMOgIHVuIG1vZMOobGUgYmF5w6lzaWVuIHF1aSBjb25kaXRpb25uZSBsZXMgdmFsZXVycyBkZSBsJ2Vuc2VtYmxlIHN1ciBsYSAqdGVtcMOpcmF0dXJlIHZyYWllKiBxdWUgaidhaSBhcHBlbGzDqSAkXHRoZXRhJCBjYXIgYycgZXN0ICBsYSBub3RhdGlvbiBjb252ZW50aW9ubmVsbGUgZGUgbCfDqXRhdCBkZSBsYSBuYXR1cmUgZCd1biBwcm9ibMOobWUgc3RhdGlzdGlxdWUuIEplIHByb3Bvc2UgOgokJCBcdGhldGEgXHNpbSBOKDAsMSkgXFwKIFxiYXJ7WH1cdmVydCAoVl4yLFx0aGV0YSkgXHNpbSBOKGFcdGltZXMgXHRoZXRhICtiICwgYysgZFx0aW1lcyBWXjIpIFxcIGxvZyhWXjIpIFxzaW0gTihnLHNeMikKICQkCgogCiBMJ2luZsOpcmVuY2UgZGVzIGNvZWZmaWNpZW50cyAkYSxiLGMsZCxnLHNeMiQpIHNlcmEgbWVuw6llIHN1ciB1biDDqWNoYW50aWxsb24gZCdhcHByZW50aXNzYWdlLgogCiBMYSBsb2kgcHLDqWRpY3RpdmUgKGMtw6AtZCBjb25kaXRpb25uZWxsZSBzYWNoYW50IGxlcyByw6lzdW3DqXMgZGUgbCdlbnNlbWJsZSAkXGJhcntYfSQgZXQgJFZeMiQpIGVzdCBpY2kgImltbcOpZGlhdGUiLCBwYXIgY29uanVnYWlzb24gbm9ybWFsZToKIAogJCRcdGhldGEgXHZlcnQgKFxiYXJ7WH0sVl4yLGEsYixjLGQpIFxzaW0gTlxsZWZ0XHsge1xsZWZ0KCB7XGZyYWN7e1xiYXIgWH19e2F9IC0gYn0gXHJpZ2h0KVxmcmFje3t7YV4yfX19e3t7YV4yfSArIGMgKyBke1ZeMn19fSxcZnJhY3t7YyArIGR7Vl4yfX19e3t7YV4yfSArIGMgKyBke1ZeMn19fX0gXHJpZ2h0XH0kJAogCgpTZXMgcGVyZm9ybWFuY2VzIHNlcm9udCBhbmFseXPDqWVzIHN1ciB1biDDqWNoYW50aWxsb24gZGUgdmFsaWRhdGlvbi4gClByZW5vbnMgcGFyIGV4ZW1wbGUgbCfDqWNoZWFuY2Ugw6AgNCBqb3VycyBkZXMgYW5uw6llcyAyMDA1LTIwMDguCgpgYGB7cn0KZCAlPiUgZmlsdGVyKFllYXIgPCAyMDEwLCBFY2hlYW5jZSA9PSA0LCAhaXMubmEoQ2FsZW5kYWlyZSkpICU+JSAKICBtdXRhdGUodGhldGE9KE9icy1UX21veV9yZWZfbGlzKS9UX3N0ZF9yZWZfbGlzLAogICAgICAgICB4YmFyPShYYmFyLVRfbW95X3JlZl9saXMpL1Rfc3RkX3JlZl9saXMsCiAgICAgICAgIHYyPSAgVjIvKFRfc3RkX3JlZl9saXNeMikgKSAlPiUgCiAgZHBseXI6OnNlbGVjdCh0aGV0YSx4YmFyLHYyKSAlPiUgZmlsdGVyKCFpcy5uYSh0aGV0YSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKHhiYXIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh2MikpIC0+IAogIGxlYXJuaW5nX3NhbXBsZQpgYGAKCgpPbiB2YSByw6lhbGlzZXIgbCdpbmbDqXJlbmNlIGJhecOpc2llbm5lIGRlcyBjb2VmZmljaWVudHMgJGEsYixjLGQkCgoKYGBge3J9Cm1vZGVsX3N0cmluZyA8LSAiCm1vZGVsewpmb3IgKGkgaW4gMTpOKXsKbVtpXSA8LSBhKnRoZXRhW2ldK2IKcHJlY2lbaV08LSAxLyhjK2QqdjJbaV0pCnhiYXJbaV0gfiBkbm9ybShtW2ldLCBwcmVjaVtpXSkKfQphIH4gZHVuaWYoLTEwLDEwKQpiIH4gZHVuaWYoLTEwLDEwKQpjIH4gZHVuaWYoMCwxMCkKZCB+IGR1bmlmKDAsMTApCn0KIgpwYXJhbXM9YygiYSIsImIiLCJjIiwiZCIpCmRhdGEgPC0gbGlzdCh0aGV0YT1sZWFybmluZ19zYW1wbGUkdGhldGEsCiAgICAgICAgICAgICB4YmFyPWxlYXJuaW5nX3NhbXBsZSR4YmFyLAogICAgICAgICAgICAgdjI9bGVhcm5pbmdfc2FtcGxlJHYyLAogICAgICAgICAgICAgTj1sZW5ndGgobGVhcm5pbmdfc2FtcGxlJHRoZXRhKSkKdGVtcDwtamFncyhkYXRhID0gZGF0YSwgbW9kZWwuZmlsZSA9IHRleHRDb25uZWN0aW9uKG1vZGVsX3N0cmluZyksCiAgICAgICAgICAgcGFyYW1ldGVycy50by5zYXZlID0gcGFyYW1zLG4uY2hhaW5zID0gMywKICAgICAgICAgICBuLmJ1cm5pbiA9IDUwMCxuLml0ZXIgPSAxMDAwKQpgYGAKCmBgYHtyfQp0ZW1wJEJVR1NvdXRwdXQkc2ltcy5tYXRyaXggJT4lIAogIGFzLmRhdGEuZnJhbWUoKSAlPiUgZHBseXI6OnNlbGVjdChhLGIsYyxkKSAtPmFiY2QKICBhYmNkICU+JSBnYXRoZXIocGFyYW0sIHZhbHVlKSAlPiUgCiAgZ2dwbG90KGFlc19zdHJpbmcoeD0idmFsdWUiKSkgKwogIGdlb21fZGVuc2l0eShhbHBoYT0wLjUpICsKICBmYWNldF9ncmlkKHBhcmFtfi4sc2NhbGVzID0gImZyZWVfeCIpIAoKYGBgCgpMZXMgY29lZmZpY2llbnRzICRhLGIsYyQgc29udCBhc3NleiBwcsOpY2lzw6ltZW50IGVzdGltw6lzIHNhdWYgbGEgdmFsZXVyIGRlICRkJCBxdWkgZXN0IHBsdXMgaW5jZXJ0YWluZSwgdGFuZGlzIHF1ZSBsZXMgY29lZmZpY2llbnRzICRjJCBldCAkZCQgc29udCBhbnRpLWNvcnLDqWzDqXMuCgpgYGB7cn0Ka25pdHI6OmthYmxlKHJiaW5kKG1lYW49YXBwbHkoWCA9IGFiY2QsMixtZWFuKSxzdGQ9YXBwbHkoWCA9IGFiY2QsMix2YXIpXjAuNSxhcHBseShYID0gYWJjZCwyLCBxdWFudGlsZSwgcHJvYnM9YygwLjA1LDAuMjUsMC41LCAwLjc1LCAwLjk1KSkpLCBkaWc9MykKa25pdHI6OmthYmxlKGNvcihhYmNkKSxkaWc9MykKYGBgCgojIyBQcmVkaWN0aXZlIGJhecOpc2llbm5lCgpTdXIgdW4gw6ljaGFudGlsbG9uIGRlIHZhbGlkYXRpb24gKGljaSAyMDExLTIwMTUpLCBvbiB2YSDDqXR1ZGllciBsZSBjb21wb3J0ZW1lbnQgZHUgcHLDqWRpY3RldXIgYmF5w6lzaWVuLgogJCRcdGhldGEgXHZlcnQgKFxiYXJ7WH0sVl4yLGEsYixjLGQpIFxzaW0gTlxsZWZ0XHsge1xsZWZ0KCB7XGZyYWN7e1xiYXIgWH0tYn17YX19IFxyaWdodClcZnJhY3t7e2FeMn19fXt7e2FeMn0gKyBjICsgZHtWXjJ9fX0sXGZyYWN7e2MgKyBke1ZeMn19fXt7e2FeMn0gKyBjICsgZHtWXjJ9fX19IFxyaWdodFx9JCQKIAoKYGBge3J9CmQgJT4lIGZpbHRlcihZZWFyID4gMjAxMCwgRWNoZWFuY2UgPT0gNCwgIWlzLm5hKENhbGVuZGFpcmUpKSAlPiUgCiAgbXV0YXRlKHRoZXRhPShPYnMtVF9tb3lfcmVmX2xpcykvVF9zdGRfcmVmX2xpcywKICAgICAgICAgeGJhcj0oWGJhci1UX21veV9yZWZfbGlzKS9UX3N0ZF9yZWZfbGlzLAogICAgICAgICB2Mj0gIFYyLyhUX3N0ZF9yZWZfbGlzXjIpICkgJT4lIAogIGZpbHRlcighaXMubmEodGhldGEpLCFpcy5uYSh4YmFyKSwhaXMubmEodjIpKSAtPiAKICB2YWxpZGF0aW9uX3NhbXBsZQppbmRpY2VzPC1zYW1wbGUoeCA9IDE6ZGltKGFiY2QpWzFdLCBzaXplPTUwLCByZXA9RikKQT1hYmNkW2luZGljZXMsMV0KQj1hYmNkW2luZGljZXMsMl0KbT0ob3V0ZXIodmFsaWRhdGlvbl9zYW1wbGUkeGJhcixBLCcvJyktQi9BKQpDPWFiY2RbaW5kaWNlcywzXQpEPWFiY2RbaW5kaWNlcyw0XQpzPW91dGVyKHZhbGlkYXRpb25fc2FtcGxlJHYyLEQsJyonKStDCm09bSpBKkEvKEFeMitzKQpzPShzL3MrQSpBKV4wLjUKWVBSRUQ9bWF0cml4KAogIHJub3JtKG4gPSBwcm9kKGRpbShzKSksIG1lYW4gPSBtLCBzZCA9IHMpLAogIG5yPWRpbShzKVsxXSxuYz1kaW0ocylbMl0gKSoKICB2YWxpZGF0aW9uX3NhbXBsZSRUX3N0ZF9yZWZfbGlzK3ZhbGlkYXRpb25fc2FtcGxlJFRfbW95X3JlZl9saXMKbW95UFJFRD1hcHBseShZUFJFRCwgMSwgbWVhbikKc3RkUFJFRD1hcHBseShZUFJFRCwgMSwgc2QpCnJtKFlQUkVEKQpgYGAK